mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
added fraction units and fixed rounding in layout
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
from typing import Callable, NamedTuple
|
from typing import Callable, NamedTuple
|
||||||
|
|
||||||
from .css.styles import StylesBase
|
from .css.styles import StylesBase
|
||||||
@@ -9,7 +10,9 @@ from .geometry import Size, Spacing
|
|||||||
class BoxModel(NamedTuple):
|
class BoxModel(NamedTuple):
|
||||||
"""The result of `get_box_model`."""
|
"""The result of `get_box_model`."""
|
||||||
|
|
||||||
size: Size # Content + padding + border
|
# Content + padding + border
|
||||||
|
width: Fraction
|
||||||
|
height: Fraction
|
||||||
margin: Spacing # Additional margin
|
margin: Spacing # Additional margin
|
||||||
|
|
||||||
|
|
||||||
@@ -17,6 +20,7 @@ def get_box_model(
|
|||||||
styles: StylesBase,
|
styles: StylesBase,
|
||||||
container: Size,
|
container: Size,
|
||||||
viewport: Size,
|
viewport: Size,
|
||||||
|
fraction_unit: Fraction,
|
||||||
get_content_width: Callable[[Size, Size], int],
|
get_content_width: Callable[[Size, Size], int],
|
||||||
get_content_height: Callable[[Size, Size, int], int],
|
get_content_height: Callable[[Size, Size, int], int],
|
||||||
) -> BoxModel:
|
) -> BoxModel:
|
||||||
@@ -32,7 +36,9 @@ def get_box_model(
|
|||||||
Returns:
|
Returns:
|
||||||
BoxModel: A tuple with the size of the content area and margin.
|
BoxModel: A tuple with the size of the content area and margin.
|
||||||
"""
|
"""
|
||||||
content_width, content_height = container
|
_content_width, _content_height = container
|
||||||
|
content_width = Fraction(_content_width)
|
||||||
|
content_height = Fraction(_content_height)
|
||||||
is_border_box = styles.box_sizing == "border-box"
|
is_border_box = styles.box_sizing == "border-box"
|
||||||
gutter = styles.gutter
|
gutter = styles.gutter
|
||||||
margin = styles.margin
|
margin = styles.margin
|
||||||
@@ -47,57 +53,67 @@ def get_box_model(
|
|||||||
|
|
||||||
if styles.width is None:
|
if styles.width is None:
|
||||||
# No width specified, fill available space
|
# No width specified, fill available space
|
||||||
content_width = content_container.width - margin.width
|
content_width = Fraction(content_container.width - margin.width)
|
||||||
elif is_auto_width:
|
elif is_auto_width:
|
||||||
# When width is auto, we want enough space to always fit the content
|
# When width is auto, we want enough space to always fit the content
|
||||||
content_width = get_content_width(
|
content_width = Fraction(
|
||||||
content_container - styles.margin.totals, viewport
|
get_content_width(content_container - styles.margin.totals, viewport)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# An explicit width
|
# An explicit width
|
||||||
content_width = styles.width.resolve_dimension(sizing_container, viewport)
|
content_width = styles.width.resolve_dimension(
|
||||||
|
sizing_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
if is_border_box:
|
if is_border_box:
|
||||||
content_width -= gutter.width
|
content_width -= gutter.width
|
||||||
|
|
||||||
if styles.min_width is not None:
|
if styles.min_width is not None:
|
||||||
# Restrict to minimum width, if set
|
# Restrict to minimum width, if set
|
||||||
min_width = styles.min_width.resolve_dimension(content_container, viewport)
|
min_width = styles.min_width.resolve_dimension(
|
||||||
|
content_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
content_width = max(content_width, min_width)
|
content_width = max(content_width, min_width)
|
||||||
|
|
||||||
if styles.max_width is not None:
|
if styles.max_width is not None:
|
||||||
# Restrict to maximum width, if set
|
# Restrict to maximum width, if set
|
||||||
max_width = styles.max_width.resolve_dimension(content_container, viewport)
|
max_width = styles.max_width.resolve_dimension(
|
||||||
|
content_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
content_width = min(content_width, max_width)
|
content_width = min(content_width, max_width)
|
||||||
|
|
||||||
content_width = max(1, content_width)
|
content_width = max(Fraction(1), content_width)
|
||||||
|
|
||||||
if styles.height is None:
|
if styles.height is None:
|
||||||
# No height specified, fill the available space
|
# No height specified, fill the available space
|
||||||
content_height = content_container.height - margin.height
|
content_height = Fraction(content_container.height - margin.height)
|
||||||
elif is_auto_height:
|
elif is_auto_height:
|
||||||
# Calculate dimensions based on content
|
# Calculate dimensions based on content
|
||||||
content_height = get_content_height(content_container, viewport, content_width)
|
content_height = Fraction(
|
||||||
|
get_content_height(content_container, viewport, int(content_width))
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Explicit height set
|
# Explicit height set
|
||||||
content_height = styles.height.resolve_dimension(sizing_container, viewport)
|
content_height = styles.height.resolve_dimension(
|
||||||
|
sizing_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
if is_border_box:
|
if is_border_box:
|
||||||
content_height -= gutter.height
|
content_height -= gutter.height
|
||||||
|
|
||||||
if styles.min_height is not None:
|
if styles.min_height is not None:
|
||||||
# Restrict to minimum height, if set
|
# Restrict to minimum height, if set
|
||||||
min_height = styles.min_height.resolve_dimension(content_container, viewport)
|
min_height = styles.min_height.resolve_dimension(
|
||||||
|
content_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
content_height = max(content_height, min_height)
|
content_height = max(content_height, min_height)
|
||||||
|
|
||||||
if styles.max_height is not None:
|
if styles.max_height is not None:
|
||||||
# Restrict maximum height, if set
|
# Restrict maximum height, if set
|
||||||
max_height = styles.max_height.resolve_dimension(content_container, viewport)
|
max_height = styles.max_height.resolve_dimension(
|
||||||
|
content_container, viewport, fraction_unit
|
||||||
|
)
|
||||||
content_height = min(content_height, max_height)
|
content_height = min(content_height, max_height)
|
||||||
|
|
||||||
content_height = max(1, content_height)
|
content_height = max(Fraction(1), content_height)
|
||||||
|
|
||||||
# Get box dimensions by adding gutter
|
model = BoxModel(content_width, content_height, margin)
|
||||||
|
|
||||||
size = Size(content_width, content_height) + gutter.totals
|
|
||||||
|
|
||||||
model = BoxModel(size, margin)
|
|
||||||
return model
|
return model
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
|
from fractions import Fraction
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import re
|
import re
|
||||||
from typing import Iterable, NamedTuple, TYPE_CHECKING
|
from typing import Iterable, NamedTuple
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
|
|
||||||
from ..geometry import Offset
|
from ..geometry import Offset, Size
|
||||||
|
|
||||||
|
|
||||||
class ScalarError(Exception):
|
class ScalarError(Exception):
|
||||||
@@ -24,6 +25,8 @@ class ScalarParseError(ScalarError):
|
|||||||
|
|
||||||
@unique
|
@unique
|
||||||
class Unit(Enum):
|
class Unit(Enum):
|
||||||
|
"""Enumeration of the various units inherited from CSS."""
|
||||||
|
|
||||||
CELLS = 1
|
CELLS = 1
|
||||||
FRACTION = 2
|
FRACTION = 2
|
||||||
PERCENT = 3
|
PERCENT = 3
|
||||||
@@ -48,18 +51,117 @@ SYMBOL_UNIT = {v: k for k, v in UNIT_SYMBOL.items()}
|
|||||||
|
|
||||||
_MATCH_SCALAR = re.compile(r"^(-?\d+\.?\d*)(fr|%|w|h|vw|vh)?$").match
|
_MATCH_SCALAR = re.compile(r"^(-?\d+\.?\d*)(fr|%|w|h|vw|vh)?$").match
|
||||||
|
|
||||||
RESOLVE_MAP = {
|
|
||||||
Unit.CELLS: lambda value, size, viewport: value,
|
|
||||||
Unit.WIDTH: lambda value, size, viewport: size[0] * value / 100,
|
|
||||||
Unit.HEIGHT: lambda value, size, viewport: size[1] * value / 100,
|
|
||||||
Unit.VIEW_WIDTH: lambda value, size, viewport: viewport[0] * value / 100,
|
|
||||||
Unit.VIEW_HEIGHT: lambda value, size, viewport: viewport[1] * value / 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
def _resolve_cells(
|
||||||
from ..widget import Widget
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
from .styles import Styles
|
) -> Fraction:
|
||||||
from .._animator import EasingFunction
|
"""Resolves explicit cell size, i.e. width: 10
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return Fraction(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_fraction(
|
||||||
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> Fraction:
|
||||||
|
"""Resolves a fraction unit i.e. width: 2fr
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return fraction_unit * Fraction(value)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_width(
|
||||||
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> Fraction:
|
||||||
|
"""Resolves width unit i.e. width: 50w.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return Fraction(value) * Fraction(size.width, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_height(
|
||||||
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> Fraction:
|
||||||
|
"""Resolves height unit, i.e. height: 12h.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return Fraction(value) * Fraction(size.height, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_view_width(
|
||||||
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> Fraction:
|
||||||
|
"""Resolves view width unit, i.e. width: 25vw.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return Fraction(value) * Fraction(viewport.width, 100)
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_view_height(
|
||||||
|
value: float, size: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> Fraction:
|
||||||
|
"""Resolves view height unit, i.e. height: 25vh.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (float): Scalar value.
|
||||||
|
size (Size): Size of widget.
|
||||||
|
viewport (Size): Size of viewport.
|
||||||
|
fraction_unit (Fraction): Size of fraction, i.e. size of 1fr as a Fraction.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Fraction: Resolved unit.
|
||||||
|
"""
|
||||||
|
return Fraction(value) * Fraction(viewport.height, 100)
|
||||||
|
|
||||||
|
|
||||||
|
RESOLVE_MAP = {
|
||||||
|
Unit.CELLS: _resolve_cells,
|
||||||
|
Unit.FRACTION: _resolve_fraction,
|
||||||
|
Unit.WIDTH: _resolve_width,
|
||||||
|
Unit.HEIGHT: _resolve_height,
|
||||||
|
Unit.VIEW_WIDTH: _resolve_view_width,
|
||||||
|
Unit.VIEW_HEIGHT: _resolve_view_height,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_symbols(units: Iterable[Unit]) -> list[str]:
|
def get_symbols(units: Iterable[Unit]) -> list[str]:
|
||||||
@@ -89,24 +191,34 @@ class Scalar(NamedTuple):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_percent(self) -> bool:
|
def is_percent(self) -> bool:
|
||||||
|
"""Check if the Scalar is a percentage unit."""
|
||||||
return self.unit == Unit.PERCENT
|
return self.unit == Unit.PERCENT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_fraction(self) -> bool:
|
||||||
|
"""Check if the unit is a fraction."""
|
||||||
|
return self.unit == Unit.FRACTION
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cells(self) -> int | None:
|
def cells(self) -> int | None:
|
||||||
|
"""Check if the unit is explicit cells."""
|
||||||
value, unit, _ = self
|
value, unit, _ = self
|
||||||
return int(value) if unit == Unit.CELLS else None
|
return int(value) if unit == Unit.CELLS else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fraction(self) -> int | None:
|
def fraction(self) -> int | None:
|
||||||
|
"""Get the fraction value, or None if not a value."""
|
||||||
value, unit, _ = self
|
value, unit, _ = self
|
||||||
return int(value) if unit == Unit.FRACTION else None
|
return int(value) if unit == Unit.FRACTION else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def symbol(self) -> str:
|
def symbol(self) -> str:
|
||||||
|
"""Get the symbol of this unit."""
|
||||||
return UNIT_SYMBOL[self.unit]
|
return UNIT_SYMBOL[self.unit]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_auto(self) -> bool:
|
def is_auto(self) -> bool:
|
||||||
|
"""Check if this is an auto unit."""
|
||||||
return self.unit == Unit.AUTO
|
return self.unit == Unit.AUTO
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -138,8 +250,8 @@ class Scalar(NamedTuple):
|
|||||||
|
|
||||||
@lru_cache(maxsize=4096)
|
@lru_cache(maxsize=4096)
|
||||||
def resolve_dimension(
|
def resolve_dimension(
|
||||||
self, size: tuple[int, int], viewport: tuple[int, int]
|
self, size: Size, viewport: Size, fraction_unit: Fraction | None = None
|
||||||
) -> int:
|
) -> Fraction:
|
||||||
"""Resolve scalar with units in to a dimensions.
|
"""Resolve scalar with units in to a dimensions.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -157,7 +269,9 @@ class Scalar(NamedTuple):
|
|||||||
if unit == Unit.PERCENT:
|
if unit == Unit.PERCENT:
|
||||||
unit = percent_unit
|
unit = percent_unit
|
||||||
try:
|
try:
|
||||||
dimension = int(RESOLVE_MAP[unit](value, size, viewport))
|
dimension = RESOLVE_MAP[unit](
|
||||||
|
value, size, viewport, fraction_unit or Fraction(1)
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ScalarResolveError(f"expected dimensions; found {str(self)!r}")
|
raise ScalarResolveError(f"expected dimensions; found {str(self)!r}")
|
||||||
return dimension
|
return dimension
|
||||||
@@ -184,6 +298,8 @@ class Scalar(NamedTuple):
|
|||||||
|
|
||||||
@rich.repr.auto(angular=True)
|
@rich.repr.auto(angular=True)
|
||||||
class ScalarOffset(NamedTuple):
|
class ScalarOffset(NamedTuple):
|
||||||
|
"""An Offset with two scalars, used to animate between to Scalars."""
|
||||||
|
|
||||||
x: Scalar
|
x: Scalar
|
||||||
y: Scalar
|
y: Scalar
|
||||||
|
|
||||||
@@ -200,7 +316,7 @@ class ScalarOffset(NamedTuple):
|
|||||||
yield None, str(self.x)
|
yield None, str(self.x)
|
||||||
yield None, str(self.y)
|
yield None, str(self.y)
|
||||||
|
|
||||||
def resolve(self, size: tuple[int, int], viewport: tuple[int, int]) -> Offset:
|
def resolve(self, size: Size, viewport: Size) -> Offset:
|
||||||
x, y = self
|
x, y = self
|
||||||
return Offset(
|
return Offset(
|
||||||
round(x.resolve_dimension(size, viewport)),
|
round(x.resolve_dimension(size, viewport)),
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ class DOMNode(MessagePump):
|
|||||||
node.id = node_id
|
node.id = node_id
|
||||||
|
|
||||||
def walk_children(self, with_self: bool = True) -> Iterable[DOMNode]:
|
def walk_children(self, with_self: bool = True) -> Iterable[DOMNode]:
|
||||||
"""Generate all descendents of this node.
|
"""Generate all descendants of this node.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
with_self (bool, optional): Also include self in the results. Defaults to True.
|
with_self (bool, optional): Also include self in the results. Defaults to True.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
|
||||||
from textual.geometry import Size, Offset, Region
|
from textual.geometry import Size, Offset, Region
|
||||||
@@ -23,8 +24,15 @@ class HorizontalLayout(Layout):
|
|||||||
x = max_width = max_height = 0
|
x = max_width = max_height = 0
|
||||||
parent_size = parent.size
|
parent_size = parent.size
|
||||||
|
|
||||||
|
children = list(parent.children)
|
||||||
|
styles = [child.styles for child in children if child.styles.width is not None]
|
||||||
|
total_fraction = sum(
|
||||||
|
[int(style.width.value) for style in styles if style.width.is_fraction]
|
||||||
|
)
|
||||||
|
fraction_unit = Fraction(size.height, total_fraction or 1)
|
||||||
|
|
||||||
box_models = [
|
box_models = [
|
||||||
widget.get_box_model(size, parent_size)
|
widget.get_box_model(size, parent_size, fraction_unit)
|
||||||
for widget in cast("list[Widget]", parent.children)
|
for widget in cast("list[Widget]", parent.children)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -40,12 +48,18 @@ class HorizontalLayout(Layout):
|
|||||||
displayed_children = parent.displayed_children
|
displayed_children = parent.displayed_children
|
||||||
|
|
||||||
for widget, box_model, margin in zip(displayed_children, box_models, margins):
|
for widget, box_model, margin in zip(displayed_children, box_models, margins):
|
||||||
content_width, content_height = box_model.size
|
content_width, content_height, box_margin = box_model
|
||||||
offset_y = widget.styles.align_height(content_height, parent_size.height)
|
offset_y = (
|
||||||
region = Region(x, offset_y, content_width, content_height)
|
widget.styles.align_height(
|
||||||
|
int(content_height), size.height - box_margin.height
|
||||||
|
)
|
||||||
|
+ box_model.margin.top
|
||||||
|
)
|
||||||
|
next_x = x + content_width
|
||||||
|
region = Region(int(x), offset_y, int(next_x - int(x)), int(content_height))
|
||||||
max_height = max(max_height, content_height)
|
max_height = max(max_height, content_height)
|
||||||
add_placement(WidgetPlacement(region, widget, 0))
|
add_placement(WidgetPlacement(region, widget, 0))
|
||||||
x += region.width + margin
|
x = next_x + margin
|
||||||
max_width = x
|
max_width = x
|
||||||
|
|
||||||
total_region = Region(0, 0, max_width, max_height)
|
total_region = Region(0, 0, max_width, max_height)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
from typing import cast, TYPE_CHECKING
|
from typing import cast, TYPE_CHECKING
|
||||||
|
|
||||||
from ..geometry import Region, Size
|
from ..geometry import Region, Size
|
||||||
@@ -21,8 +22,15 @@ class VerticalLayout(Layout):
|
|||||||
|
|
||||||
parent_size = parent.size
|
parent_size = parent.size
|
||||||
|
|
||||||
|
children = list(parent.children)
|
||||||
|
styles = [child.styles for child in children if child.styles.height is not None]
|
||||||
|
total_fraction = sum(
|
||||||
|
[int(style.height.value) for style in styles if style.height.is_fraction]
|
||||||
|
)
|
||||||
|
fraction_unit = Fraction(size.height, total_fraction or 1)
|
||||||
|
|
||||||
box_models = [
|
box_models = [
|
||||||
widget.get_box_model(size, parent_size)
|
widget.get_box_model(size, parent_size, fraction_unit)
|
||||||
for widget in cast("list[Widget]", parent.children)
|
for widget in cast("list[Widget]", parent.children)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -33,22 +41,23 @@ class VerticalLayout(Layout):
|
|||||||
if box_models:
|
if box_models:
|
||||||
margins.append(box_models[-1].margin.bottom)
|
margins.append(box_models[-1].margin.bottom)
|
||||||
|
|
||||||
y = box_models[0].margin.top if box_models else 0
|
y = Fraction(box_models[0].margin.top if box_models else 0)
|
||||||
|
|
||||||
displayed_children = cast("list[Widget]", parent.displayed_children)
|
displayed_children = cast("list[Widget]", parent.displayed_children)
|
||||||
for widget, box_model, margin in zip(displayed_children, box_models, margins):
|
for widget, box_model, margin in zip(displayed_children, box_models, margins):
|
||||||
content_width, content_height = box_model.size
|
content_width, content_height, box_margin = box_model
|
||||||
offset_x = (
|
offset_x = (
|
||||||
widget.styles.align_width(
|
widget.styles.align_width(
|
||||||
content_width, size.width - box_model.margin.width
|
int(content_width), size.width - box_margin.width
|
||||||
)
|
)
|
||||||
+ box_model.margin.left
|
+ box_model.margin.left
|
||||||
)
|
)
|
||||||
region = Region(offset_x, y, content_width, content_height)
|
next_y = y + content_height
|
||||||
|
region = Region(offset_x, int(y), int(content_width), int(next_y - int(y)))
|
||||||
add_placement(WidgetPlacement(region, widget, 0))
|
add_placement(WidgetPlacement(region, widget, 0))
|
||||||
y += region.height + margin
|
y = next_y + margin
|
||||||
|
|
||||||
total_region = Region(0, 0, size.width, y)
|
total_region = Region(0, 0, size.width, int(y))
|
||||||
add_placement(WidgetPlacement(total_region, None, 0))
|
add_placement(WidgetPlacement(total_region, None, 0))
|
||||||
|
|
||||||
return placements, set(displayed_children)
|
return placements, set(displayed_children)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from fractions import Fraction
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Awaitable,
|
Awaitable,
|
||||||
@@ -155,7 +156,9 @@ class Widget(DOMNode):
|
|||||||
self.CSS, f"{__file__}:<{self.__class__.__name__}>"
|
self.CSS, f"{__file__}:<{self.__class__.__name__}>"
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_box_model(self, container: Size, viewport: Size) -> BoxModel:
|
def get_box_model(
|
||||||
|
self, container: Size, viewport: Size, fraction_unit: Fraction
|
||||||
|
) -> BoxModel:
|
||||||
"""Process the box model for this widget.
|
"""Process the box model for this widget.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -169,6 +172,7 @@ class Widget(DOMNode):
|
|||||||
self.styles,
|
self.styles,
|
||||||
container,
|
container,
|
||||||
viewport,
|
viewport,
|
||||||
|
fraction_unit,
|
||||||
self.get_content_width,
|
self.get_content_width,
|
||||||
self.get_content_height,
|
self.get_content_height,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user