mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
optimized layout_resolve added tests
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from fractions import Fraction
|
||||
from typing import cast, List, Optional, Sequence
|
||||
from typing import cast, Sequence
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Protocol
|
||||
@@ -13,12 +13,12 @@ else:
|
||||
class Edge(Protocol):
|
||||
"""Any object that defines an edge (such as Layout)."""
|
||||
|
||||
size: Optional[int] = None
|
||||
size: int | None
|
||||
fraction: int = 1
|
||||
min_size: int = 1
|
||||
|
||||
|
||||
def layout_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
|
||||
def layout_resolve(total: int, edges: Sequence[Edge]) -> list[int]:
|
||||
"""Divide total space to satisfy size, fraction, and min_size, constraints.
|
||||
|
||||
The returned list of integers should add up to total in most cases, unless it is
|
||||
@@ -37,33 +37,37 @@ def layout_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
|
||||
# Size of edge or None for yet to be determined
|
||||
sizes = [(edge.size or None) for edge in edges]
|
||||
|
||||
_Fraction = Fraction
|
||||
if None not in sizes:
|
||||
return cast(list[int], sizes)
|
||||
|
||||
# While any edges haven't been calculated
|
||||
while None in sizes:
|
||||
# Get flexible edges and index to map these back on to sizes list
|
||||
flexible_edges = [
|
||||
(index, edge)
|
||||
for index, (size, edge) in enumerate(zip(sizes, edges))
|
||||
if size is None
|
||||
# Get flexible edges and index to map these back on to sizes list
|
||||
flexible_edges = [
|
||||
(index, edge)
|
||||
for index, (size, edge) in enumerate(zip(sizes, edges))
|
||||
if size is None
|
||||
]
|
||||
# Remaining space in total
|
||||
remaining = total - sum(size or 0 for size in sizes)
|
||||
if remaining <= 0:
|
||||
# No room for flexible edges
|
||||
return [
|
||||
((edge.min_size or 1) if size is None else size)
|
||||
for size, edge in zip(sizes, edges)
|
||||
]
|
||||
# Remaining space in total
|
||||
remaining = total - sum(size or 0 for size in sizes)
|
||||
if remaining <= 0:
|
||||
# No room for flexible edges
|
||||
return [
|
||||
((edge.min_size or 1) if size is None else size)
|
||||
for size, edge in zip(sizes, edges)
|
||||
]
|
||||
|
||||
_Fraction = Fraction
|
||||
while None in sizes:
|
||||
# Calculate number of characters in a ratio portion
|
||||
portion = _Fraction(
|
||||
remaining, sum((edge.fraction or 1) for _, edge in flexible_edges)
|
||||
)
|
||||
|
||||
# If any edges will be less than their minimum, replace size with the minimum
|
||||
for index, edge in flexible_edges:
|
||||
for flexible_index, (index, edge) in enumerate(flexible_edges):
|
||||
if portion * edge.fraction <= edge.min_size:
|
||||
sizes[index] = edge.min_size
|
||||
remaining -= edge.min_size
|
||||
del flexible_edges[flexible_index]
|
||||
# New fixed size will invalidate calculations, so we need to repeat the process
|
||||
break
|
||||
else:
|
||||
@@ -72,9 +76,8 @@ def layout_resolve(total: int, edges: Sequence[Edge]) -> List[int]:
|
||||
# to the following line
|
||||
remainder = _Fraction(0)
|
||||
for index, edge in flexible_edges:
|
||||
size, remainder = divmod(portion * edge.fraction + remainder, 1)
|
||||
sizes[index] = size
|
||||
sizes[index], remainder = divmod(portion * edge.fraction + remainder, 1)
|
||||
break
|
||||
|
||||
# Sizes now contains integers only
|
||||
return cast(List[int], sizes)
|
||||
return cast(list[int], sizes)
|
||||
|
||||
64
tests/test_layout_resolve.py
Normal file
64
tests/test_layout_resolve.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
import pytest
|
||||
from textual._layout_resolve import layout_resolve
|
||||
|
||||
|
||||
class Edge(NamedTuple):
|
||||
size: int | None = None
|
||||
fraction: int = 1
|
||||
min_size: int = 1
|
||||
|
||||
|
||||
def test_single():
|
||||
# One edge fixed size
|
||||
assert layout_resolve(100, [Edge(10)]) == [10]
|
||||
# One edge fraction of 1
|
||||
assert layout_resolve(100, [Edge(None, 1)]) == [100]
|
||||
# One edge fraction 3
|
||||
assert layout_resolve(100, [Edge(None, 2)]) == [100]
|
||||
# One edge, fraction1, min size 20
|
||||
assert layout_resolve(100, [Edge(None, 1, 20)]) == [100]
|
||||
# One edge fraction 1, min size 120
|
||||
assert layout_resolve(100, [Edge(None, 1, 120)]) == [120]
|
||||
|
||||
|
||||
def test_two():
|
||||
# Two edges fixed size
|
||||
assert layout_resolve(100, [Edge(10), Edge(20)]) == [10, 20]
|
||||
# Two edges, fraction 1 each
|
||||
assert layout_resolve(100, [Edge(None, 1), Edge(None, 1)]) == [50, 50]
|
||||
# Two edges, one with fraction 2, one with fraction 1
|
||||
# Note first value is rounded down, second is rounded up
|
||||
assert layout_resolve(100, [Edge(None, 2), Edge(None, 1)]) == [66, 34]
|
||||
# Two edges, both with fraction 2
|
||||
assert layout_resolve(100, [Edge(None, 2), Edge(None, 2)]) == [50, 50]
|
||||
# Two edges, one with fraction 3, one with fraction 1
|
||||
assert layout_resolve(100, [Edge(None, 3), Edge(None, 1)]) == [75, 25]
|
||||
# Two edges, one with fraction 3, one with fraction 1, second with min size of 30
|
||||
assert layout_resolve(100, [Edge(None, 3), Edge(None, 1, 30)]) == [70, 30]
|
||||
# Two edges, one with fraction 1 and min size 30, one with fraction 3
|
||||
assert layout_resolve(100, [Edge(None, 1, 30), Edge(None, 3)]) == [30, 70]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"size, edges, result",
|
||||
[
|
||||
(10, [Edge(None, 1), Edge(None, 1), Edge(None, 1)], [3, 3, 4]),
|
||||
(10, [Edge(5), Edge(None, 1), Edge(None, 1)], [5, 2, 3]),
|
||||
(10, [Edge(None, 2), Edge(None, 1), Edge(None, 1)], [5, 2, 3]),
|
||||
(10, [Edge(None, 2), Edge(3), Edge(None, 1)], [4, 3, 3]),
|
||||
(
|
||||
10,
|
||||
[Edge(None, 2), Edge(None, 1), Edge(None, 1), Edge(None, 1)],
|
||||
[4, 2, 2, 2],
|
||||
),
|
||||
(
|
||||
10,
|
||||
[Edge(None, 4), Edge(None, 1), Edge(None, 1), Edge(None, 1)],
|
||||
[5, 2, 1, 2],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_multiple(size, edges, result):
|
||||
assert layout_resolve(size, edges) == result
|
||||
Reference in New Issue
Block a user