mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Expanding fr (#2221)
* forced fr to expand * margin size * remove comment * missing snapshot * snapshot tests * changelog * optimize * snapshot fix * snapshot update * snapshot and fixes * docstrings [skip ci]
This commit is contained in:
@@ -15,9 +15,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
- Allowed border_title and border_subtitle to accept Text objects
|
||||
- Added additional line around titles
|
||||
- When a container is auto, relative dimensions in children stretch the container. https://github.com/Textualize/textual/pull/2221
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed margin not being respected when width or height is "auto" https://github.com/Textualize/textual/issues/2220
|
||||
- Fixed issue which prevent scroll_visible from working https://github.com/Textualize/textual/issues/2181
|
||||
|
||||
## [0.18.0] - 2023-04-04
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Screen {
|
||||
align: center middle;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#buttons {
|
||||
margin-top: 1;
|
||||
height: 3;
|
||||
width: auto;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ ContentSwitcher {
|
||||
background: $panel;
|
||||
border: round $primary;
|
||||
width: 90%;
|
||||
height: 80%;
|
||||
height: 1fr;
|
||||
}
|
||||
|
||||
DataTable {
|
||||
|
||||
@@ -136,4 +136,5 @@ class Layout(ABC):
|
||||
# Use a height of zero to ignore relative heights
|
||||
arrangement = widget._arrange(Size(width, 0))
|
||||
height = arrangement.total_region.bottom
|
||||
|
||||
return height
|
||||
|
||||
@@ -33,7 +33,6 @@ def resolve(
|
||||
Returns:
|
||||
List of (<OFFSET>, <LENGTH>)
|
||||
"""
|
||||
|
||||
resolved: list[tuple[Scalar, Fraction | None]] = [
|
||||
(
|
||||
(scalar, None)
|
||||
@@ -84,6 +83,7 @@ def resolve_box_models(
|
||||
widgets: list[Widget],
|
||||
size: Size,
|
||||
parent_size: Size,
|
||||
margin: Size,
|
||||
dimension: Literal["width", "height"] = "width",
|
||||
) -> list[BoxModel]:
|
||||
"""Resolve box models for a list of dimensions
|
||||
@@ -93,14 +93,19 @@ def resolve_box_models(
|
||||
widgets: Widgets in resolve.
|
||||
size: Size of container.
|
||||
parent_size: Size of parent.
|
||||
margin: Total space occupied by margin
|
||||
dimensions: Which dimension to resolve.
|
||||
|
||||
Returns:
|
||||
List of resolved box models.
|
||||
"""
|
||||
margin_width, margin_height = margin
|
||||
|
||||
fraction_width = Fraction(max(0, size.width - margin_width))
|
||||
fraction_height = Fraction(max(0, size.height - margin_height))
|
||||
|
||||
margin_size = size - margin
|
||||
|
||||
fraction_width = Fraction(size.width)
|
||||
fraction_height = Fraction(size.height)
|
||||
box_models: list[BoxModel | None] = [
|
||||
(
|
||||
None
|
||||
@@ -116,12 +121,12 @@ def resolve_box_models(
|
||||
total_remaining = sum(
|
||||
box_model.width for box_model in box_models if box_model is not None
|
||||
)
|
||||
remaining_space = max(0, size.width - total_remaining)
|
||||
remaining_space = max(0, size.width - total_remaining - margin_width)
|
||||
else:
|
||||
total_remaining = sum(
|
||||
box_model.height for box_model in box_models if box_model is not None
|
||||
)
|
||||
remaining_space = max(0, size.height - total_remaining)
|
||||
remaining_space = max(0, size.height - total_remaining - margin_height)
|
||||
|
||||
fraction_unit = Fraction(
|
||||
remaining_space,
|
||||
@@ -136,9 +141,9 @@ def resolve_box_models(
|
||||
)
|
||||
if dimension == "width":
|
||||
width_fraction = fraction_unit
|
||||
height_fraction = Fraction(size.height)
|
||||
height_fraction = Fraction(margin_size.height)
|
||||
else:
|
||||
width_fraction = Fraction(size.width)
|
||||
width_fraction = Fraction(margin_size.width)
|
||||
height_fraction = fraction_unit
|
||||
|
||||
box_models = [
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fractions import Fraction
|
||||
from typing import Callable, NamedTuple
|
||||
from typing import NamedTuple
|
||||
|
||||
from .css.styles import StylesBase
|
||||
from .geometry import Size, Spacing
|
||||
from .geometry import Spacing
|
||||
|
||||
|
||||
class BoxModel(NamedTuple):
|
||||
@@ -14,119 +13,3 @@ class BoxModel(NamedTuple):
|
||||
width: Fraction
|
||||
height: Fraction
|
||||
margin: Spacing # Additional margin
|
||||
|
||||
|
||||
def get_box_model(
|
||||
styles: StylesBase,
|
||||
container: Size,
|
||||
viewport: Size,
|
||||
width_fraction: Fraction,
|
||||
height_fraction: Fraction,
|
||||
get_content_width: Callable[[Size, Size], int],
|
||||
get_content_height: Callable[[Size, Size, int], int],
|
||||
) -> BoxModel:
|
||||
"""Resolve the box model for this Styles.
|
||||
|
||||
Args:
|
||||
styles: Styles object.
|
||||
container: The size of the widget container.
|
||||
viewport: The viewport size.
|
||||
width_fraction: A fraction used for 1 `fr` unit on the width dimension.
|
||||
height_fraction: A fraction used for 1 `fr` unit on the height dimension.
|
||||
get_content_width: A callable which accepts container size and parent size and returns a width.
|
||||
get_content_height: A callable which accepts container size and parent size and returns a height.
|
||||
|
||||
Returns:
|
||||
A tuple with the size of the content area and margin.
|
||||
"""
|
||||
_content_width, _content_height = container
|
||||
content_width = Fraction(_content_width)
|
||||
content_height = Fraction(_content_height)
|
||||
is_border_box = styles.box_sizing == "border-box"
|
||||
gutter = styles.gutter
|
||||
margin = styles.margin
|
||||
|
||||
is_auto_width = styles.width and styles.width.is_auto
|
||||
is_auto_height = styles.height and styles.height.is_auto
|
||||
|
||||
# Container minus padding and border
|
||||
content_container = container - gutter.totals
|
||||
# The container including the content
|
||||
sizing_container = content_container if is_border_box else container
|
||||
|
||||
if styles.width is None:
|
||||
# No width specified, fill available space
|
||||
content_width = Fraction(content_container.width - margin.width)
|
||||
elif is_auto_width:
|
||||
# When width is auto, we want enough space to always fit the content
|
||||
content_width = Fraction(
|
||||
get_content_width(content_container - styles.margin.totals, viewport)
|
||||
)
|
||||
if styles.scrollbar_gutter == "stable" and styles.overflow_x == "auto":
|
||||
content_width += styles.scrollbar_size_vertical
|
||||
else:
|
||||
# An explicit width
|
||||
styles_width = styles.width
|
||||
content_width = styles_width.resolve(
|
||||
sizing_container - styles.margin.totals, viewport, width_fraction
|
||||
)
|
||||
if is_border_box and styles_width.excludes_border:
|
||||
content_width -= gutter.width
|
||||
|
||||
if styles.min_width is not None:
|
||||
# Restrict to minimum width, if set
|
||||
min_width = styles.min_width.resolve(
|
||||
content_container, viewport, width_fraction
|
||||
)
|
||||
content_width = max(content_width, min_width)
|
||||
|
||||
if styles.max_width is not None:
|
||||
# Restrict to maximum width, if set
|
||||
max_width = styles.max_width.resolve(
|
||||
content_container, viewport, width_fraction
|
||||
)
|
||||
if is_border_box:
|
||||
max_width -= gutter.width
|
||||
content_width = min(content_width, max_width)
|
||||
|
||||
content_width = max(Fraction(0), content_width)
|
||||
|
||||
if styles.height is None:
|
||||
# No height specified, fill the available space
|
||||
content_height = Fraction(content_container.height - margin.height)
|
||||
elif is_auto_height:
|
||||
# Calculate dimensions based on content
|
||||
content_height = Fraction(
|
||||
get_content_height(content_container, viewport, int(content_width))
|
||||
)
|
||||
if styles.scrollbar_gutter == "stable" and styles.overflow_y == "auto":
|
||||
content_height += styles.scrollbar_size_horizontal
|
||||
else:
|
||||
styles_height = styles.height
|
||||
# Explicit height set
|
||||
content_height = styles_height.resolve(
|
||||
sizing_container - styles.margin.totals, viewport, height_fraction
|
||||
)
|
||||
if is_border_box and styles_height.excludes_border:
|
||||
content_height -= gutter.height
|
||||
|
||||
if styles.min_height is not None:
|
||||
# Restrict to minimum height, if set
|
||||
min_height = styles.min_height.resolve(
|
||||
content_container, viewport, height_fraction
|
||||
)
|
||||
content_height = max(content_height, min_height)
|
||||
|
||||
if styles.max_height is not None:
|
||||
# Restrict maximum height, if set
|
||||
max_height = styles.max_height.resolve(
|
||||
content_container, viewport, height_fraction
|
||||
)
|
||||
content_height = min(content_height, max_height)
|
||||
|
||||
content_height = max(Fraction(0), content_height)
|
||||
|
||||
model = BoxModel(
|
||||
content_width + gutter.width, content_height + gutter.height, margin
|
||||
)
|
||||
return model
|
||||
|
||||
@@ -384,6 +384,21 @@ class StylesBase(ABC):
|
||||
has_rule("height") and self.height.is_auto # type: ignore
|
||||
)
|
||||
|
||||
@property
|
||||
def is_relative_width(self) -> bool:
|
||||
"""Does the node have a relative width?"""
|
||||
width = self.width
|
||||
return width is not None and width.unit in (
|
||||
Unit.FRACTION,
|
||||
Unit.PERCENT,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_relative_height(self) -> bool:
|
||||
"""Does the node have a relative width?"""
|
||||
height = self.height
|
||||
return height is not None and height.unit in (Unit.FRACTION, Unit.PERCENT)
|
||||
|
||||
@abstractmethod
|
||||
def has_rule(self, rule: str) -> bool:
|
||||
"""Check if a rule is set on this Styles object.
|
||||
|
||||
@@ -23,11 +23,28 @@ class HorizontalLayout(Layout):
|
||||
x = max_height = Fraction(0)
|
||||
parent_size = parent.outer_size
|
||||
|
||||
child_styles = [child.styles for child in children]
|
||||
box_margins = [styles.margin for styles in child_styles]
|
||||
if box_margins:
|
||||
resolve_margin = Size(
|
||||
(
|
||||
sum(
|
||||
max(margin1.right, margin2.left)
|
||||
for margin1, margin2 in zip(box_margins, box_margins[1:])
|
||||
)
|
||||
+ (box_margins[0].left + box_margins[-1].right)
|
||||
),
|
||||
max(margin.height for margin in box_margins),
|
||||
)
|
||||
else:
|
||||
resolve_margin = Size(0, 0)
|
||||
|
||||
box_models = resolve_box_models(
|
||||
[child.styles.width for child in children],
|
||||
[styles.width for styles in child_styles],
|
||||
children,
|
||||
size,
|
||||
parent_size,
|
||||
resolve_margin,
|
||||
dimension="width",
|
||||
)
|
||||
|
||||
|
||||
@@ -23,11 +23,28 @@ class VerticalLayout(Layout):
|
||||
add_placement = placements.append
|
||||
parent_size = parent.outer_size
|
||||
|
||||
child_styles = [child.styles for child in children]
|
||||
box_margins = [styles.margin for styles in child_styles]
|
||||
if box_margins:
|
||||
resolve_margin = Size(
|
||||
max([margin.width for margin in box_margins]),
|
||||
(
|
||||
sum(
|
||||
max(margin1.bottom, margin2.top)
|
||||
for margin1, margin2 in zip(box_margins, box_margins[1:])
|
||||
)
|
||||
+ (box_margins[0].top + box_margins[-1].bottom)
|
||||
),
|
||||
)
|
||||
else:
|
||||
resolve_margin = Size(0, 0)
|
||||
|
||||
box_models = resolve_box_models(
|
||||
[child.styles.height for child in children],
|
||||
[styles.height for styles in child_styles],
|
||||
children,
|
||||
size,
|
||||
parent_size,
|
||||
resolve_margin,
|
||||
dimension="height",
|
||||
)
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ from ._styles_cache import StylesCache
|
||||
from .actions import SkipAction
|
||||
from .await_remove import AwaitRemove
|
||||
from .binding import Binding
|
||||
from .box_model import BoxModel, get_box_model
|
||||
from .box_model import BoxModel
|
||||
from .css.query import NoMatches, WrongType
|
||||
from .css.scalar import ScalarOffset
|
||||
from .dom import DOMNode, NoScreen
|
||||
@@ -316,6 +316,7 @@ class Widget(DOMNode):
|
||||
self._rich_style_cache: dict[str, tuple[Style, Style]] = {}
|
||||
self._stabilize_scrollbar: tuple[Size, str, str] | None = None
|
||||
"""Used to prevent scrollbar logic getting stuck in an infinite loop."""
|
||||
|
||||
self._lock = Lock()
|
||||
|
||||
super().__init__(
|
||||
@@ -889,16 +890,110 @@ class Widget(DOMNode):
|
||||
Returns:
|
||||
The size and margin for this widget.
|
||||
"""
|
||||
box_model = get_box_model(
|
||||
self.styles,
|
||||
container,
|
||||
viewport,
|
||||
width_fraction,
|
||||
height_fraction,
|
||||
self.get_content_width,
|
||||
self.get_content_height,
|
||||
styles = self.styles
|
||||
_content_width, _content_height = container
|
||||
content_width = Fraction(_content_width)
|
||||
content_height = Fraction(_content_height)
|
||||
is_border_box = styles.box_sizing == "border-box"
|
||||
gutter = styles.gutter
|
||||
margin = styles.margin
|
||||
|
||||
is_auto_width = styles.width and styles.width.is_auto
|
||||
is_auto_height = styles.height and styles.height.is_auto
|
||||
|
||||
# Container minus padding and border
|
||||
content_container = container - gutter.totals
|
||||
# The container including the content
|
||||
sizing_container = content_container if is_border_box else container
|
||||
|
||||
if styles.width is None:
|
||||
# No width specified, fill available space
|
||||
content_width = Fraction(content_container.width - margin.width)
|
||||
elif is_auto_width:
|
||||
# When width is auto, we want enough space to always fit the content
|
||||
content_width = Fraction(
|
||||
self.get_content_width(
|
||||
content_container - styles.margin.totals, viewport
|
||||
)
|
||||
)
|
||||
if styles.scrollbar_gutter == "stable" and styles.overflow_x == "auto":
|
||||
content_width += styles.scrollbar_size_vertical
|
||||
if (
|
||||
content_width < content_container.width
|
||||
and self._has_relative_children_width
|
||||
):
|
||||
content_width = Fraction(content_container.width)
|
||||
else:
|
||||
# An explicit width
|
||||
styles_width = styles.width
|
||||
content_width = styles_width.resolve(
|
||||
sizing_container - styles.margin.totals, viewport, width_fraction
|
||||
)
|
||||
if is_border_box and styles_width.excludes_border:
|
||||
content_width -= gutter.width
|
||||
|
||||
if styles.min_width is not None:
|
||||
# Restrict to minimum width, if set
|
||||
min_width = styles.min_width.resolve(
|
||||
content_container, viewport, width_fraction
|
||||
)
|
||||
content_width = max(content_width, min_width)
|
||||
|
||||
if styles.max_width is not None:
|
||||
# Restrict to maximum width, if set
|
||||
max_width = styles.max_width.resolve(
|
||||
content_container, viewport, width_fraction
|
||||
)
|
||||
if is_border_box:
|
||||
max_width -= gutter.width
|
||||
content_width = min(content_width, max_width)
|
||||
|
||||
content_width = max(Fraction(0), content_width)
|
||||
|
||||
if styles.height is None:
|
||||
# No height specified, fill the available space
|
||||
content_height = Fraction(content_container.height - margin.height)
|
||||
elif is_auto_height:
|
||||
# Calculate dimensions based on content
|
||||
content_height = Fraction(
|
||||
self.get_content_height(content_container, viewport, int(content_width))
|
||||
)
|
||||
if styles.scrollbar_gutter == "stable" and styles.overflow_y == "auto":
|
||||
content_height += styles.scrollbar_size_horizontal
|
||||
if (
|
||||
content_height < content_container.height
|
||||
and self._has_relative_children_height
|
||||
):
|
||||
content_height = Fraction(content_container.height)
|
||||
else:
|
||||
styles_height = styles.height
|
||||
# Explicit height set
|
||||
content_height = styles_height.resolve(
|
||||
sizing_container - styles.margin.totals, viewport, height_fraction
|
||||
)
|
||||
if is_border_box and styles_height.excludes_border:
|
||||
content_height -= gutter.height
|
||||
|
||||
if styles.min_height is not None:
|
||||
# Restrict to minimum height, if set
|
||||
min_height = styles.min_height.resolve(
|
||||
content_container, viewport, height_fraction
|
||||
)
|
||||
content_height = max(content_height, min_height)
|
||||
|
||||
if styles.max_height is not None:
|
||||
# Restrict maximum height, if set
|
||||
max_height = styles.max_height.resolve(
|
||||
content_container, viewport, height_fraction
|
||||
)
|
||||
content_height = min(content_height, max_height)
|
||||
|
||||
content_height = max(Fraction(0), content_height)
|
||||
|
||||
model = BoxModel(
|
||||
content_width + gutter.width, content_height + gutter.height, margin
|
||||
)
|
||||
return box_model
|
||||
return model
|
||||
|
||||
def get_content_width(self, container: Size, viewport: Size) -> int:
|
||||
"""Called by textual to get the width of the content area. May be overridden in a subclass.
|
||||
@@ -1366,6 +1461,20 @@ class Widget(DOMNode):
|
||||
"""
|
||||
return active_app.get().console
|
||||
|
||||
@property
|
||||
def _has_relative_children_width(self) -> bool:
|
||||
"""Do any children have a relative width?"""
|
||||
if not self.is_container:
|
||||
return False
|
||||
return any(widget.styles.is_relative_width for widget in self.children)
|
||||
|
||||
@property
|
||||
def _has_relative_children_height(self) -> bool:
|
||||
"""Do any children have a relative height?"""
|
||||
if not self.is_container:
|
||||
return False
|
||||
return any(widget.styles.is_relative_height for widget in self.children)
|
||||
|
||||
def animate(
|
||||
self,
|
||||
attribute: str,
|
||||
|
||||
File diff suppressed because one or more lines are too long
40
tests/snapshot_tests/snapshot_apps/auto_fr.py
Normal file
40
tests/snapshot_tests/snapshot_apps/auto_fr.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Label
|
||||
|
||||
|
||||
class FRApp(App):
|
||||
CSS = """
|
||||
Screen {
|
||||
align: center middle;
|
||||
border: solid cyan;
|
||||
}
|
||||
|
||||
#container {
|
||||
width: 30;
|
||||
height: auto;
|
||||
border: solid green;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#child {
|
||||
height: 1fr;
|
||||
border: solid red;
|
||||
}
|
||||
|
||||
#bottom {
|
||||
margin: 1 2;
|
||||
background: $primary;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Widget(id="container"):
|
||||
yield Label("Hello one line", id="top")
|
||||
yield Widget(id="child")
|
||||
yield Label("Two\nLines with 1x2 margin", id="bottom")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = FRApp()
|
||||
app.run()
|
||||
35
tests/snapshot_tests/snapshot_apps/fr_margins.py
Normal file
35
tests/snapshot_tests/snapshot_apps/fr_margins.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.widgets import Label
|
||||
from textual.containers import Container
|
||||
|
||||
|
||||
# Test fr dimensions and margins work in an auto container
|
||||
# https://github.com/Textualize/textual/issues/2220
|
||||
class TestApp(App):
|
||||
CSS = """
|
||||
Container {
|
||||
background: green 20%;
|
||||
border: heavy green;
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
Label {
|
||||
background: green 20%;
|
||||
width: 1fr;
|
||||
height: 1fr;
|
||||
margin: 2 2;
|
||||
}
|
||||
"""
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Container():
|
||||
yield Label("Hello")
|
||||
yield Label("World")
|
||||
yield Label("!!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = TestApp()
|
||||
app.run()
|
||||
@@ -388,6 +388,16 @@ def test_dock_scroll(snap_compare):
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "dock_scroll.py", terminal_size=(80, 25))
|
||||
|
||||
|
||||
def test_auto_fr(snap_compare):
|
||||
# https://github.com/Textualize/textual/issues/2220
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "auto_fr.py", terminal_size=(80, 25))
|
||||
|
||||
|
||||
def test_fr_margins(snap_compare):
|
||||
# https://github.com/Textualize/textual/issues/2220
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "fr_margins.py", terminal_size=(80, 25))
|
||||
|
||||
|
||||
def test_scroll_visible(snap_compare):
|
||||
# https://github.com/Textualize/textual/issues/2181
|
||||
assert snap_compare(SNAPSHOT_APPS_DIR / "scroll_visible.py", press=["t"])
|
||||
|
||||
@@ -2,206 +2,190 @@ from __future__ import annotations
|
||||
|
||||
from fractions import Fraction
|
||||
|
||||
from textual.box_model import BoxModel, get_box_model
|
||||
from textual.box_model import BoxModel
|
||||
from textual.css.styles import Styles
|
||||
from textual.geometry import Size, Spacing
|
||||
from textual.widget import Widget
|
||||
|
||||
|
||||
def test_content_box():
|
||||
styles = Styles()
|
||||
styles.width = 10
|
||||
styles.height = 8
|
||||
styles.padding = 1
|
||||
styles.border = ("solid", "red")
|
||||
|
||||
one = Fraction(1)
|
||||
|
||||
class TestWidget(Widget):
|
||||
def get_content_width(self, container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_content_height(self, container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
widget = TestWidget()
|
||||
|
||||
# border-box is default
|
||||
assert styles.box_sizing == "border-box"
|
||||
assert widget.styles.box_sizing == "border-box"
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
widget.styles.width = 10
|
||||
widget.styles.height = 8
|
||||
widget.styles.padding = 1
|
||||
widget.styles.border = ("solid", "red")
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
box_model = widget._get_box_model(
|
||||
Size(60, 20),
|
||||
Size(80, 24),
|
||||
one,
|
||||
one,
|
||||
)
|
||||
# Size should be inclusive of padding / border
|
||||
assert box_model == BoxModel(Fraction(10), Fraction(8), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Switch to content-box
|
||||
styles.box_sizing = "content-box"
|
||||
widget.styles.box_sizing = "content-box"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
# width and height have added padding / border to accommodate content
|
||||
assert box_model == BoxModel(Fraction(14), Fraction(12), Spacing(0, 0, 0, 0))
|
||||
|
||||
|
||||
def test_width():
|
||||
"""Test width settings."""
|
||||
styles = Styles()
|
||||
|
||||
one = Fraction(1)
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
class TestWidget(Widget):
|
||||
def get_content_width(self, container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
def get_auto_height(container: Size, parent: Size, width: int) -> int:
|
||||
return 10
|
||||
def get_content_height(self, container: Size, parent: Size, width: int) -> int:
|
||||
return 10
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
widget = TestWidget()
|
||||
styles = widget.styles
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(60), Fraction(20), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Add a margin and check that it is reported
|
||||
styles.margin = (1, 2, 3, 4)
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set width to auto-detect
|
||||
styles.width = "auto"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
# Setting width to auto should call get_auto_width
|
||||
assert box_model == BoxModel(Fraction(10), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set width to 100 vw which should make it the width of the parent
|
||||
styles.width = "100vw"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(80), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set the width to 100% should make it fill the container size
|
||||
styles.width = "100%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
styles.width = "100vw"
|
||||
styles.max_width = "50%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(30), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
|
||||
def test_height():
|
||||
"""Test height settings."""
|
||||
styles = Styles()
|
||||
|
||||
one = Fraction(1)
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
class TestWidget(Widget):
|
||||
def get_content_width(self, container: Size, parent: Size) -> int:
|
||||
return 10
|
||||
|
||||
def get_auto_height(container: Size, parent: Size, width: int) -> int:
|
||||
return 10
|
||||
def get_content_height(self, container: Size, parent: Size, width: int) -> int:
|
||||
return 10
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
widget = TestWidget()
|
||||
styles = widget.styles
|
||||
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(60), Fraction(20), Spacing(0, 0, 0, 0))
|
||||
|
||||
# Add a margin and check that it is reported
|
||||
styles.margin = (1, 2, 3, 4)
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set height to 100 vw which should make it the height of the parent
|
||||
styles.height = "100vh"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(24), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set the height to 100% should make it fill the container size
|
||||
styles.height = "100%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(16), Spacing(1, 2, 3, 4))
|
||||
|
||||
styles.height = "auto"
|
||||
styles.margin = 2
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
print(box_model)
|
||||
assert box_model == BoxModel(Fraction(56), Fraction(10), Spacing(2, 2, 2, 2))
|
||||
|
||||
styles.margin = 1, 2, 3, 4
|
||||
styles.height = "100vh"
|
||||
styles.max_height = "50%"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(60, 20), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(10), Spacing(1, 2, 3, 4))
|
||||
|
||||
# Set height to auto and set content height to 0 to check if box collapses.
|
||||
styles.height = "auto"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(60, 20), Size(80, 24), one, one, get_auto_width, lambda *_: 0
|
||||
)
|
||||
assert box_model == BoxModel(Fraction(54), Fraction(0), Spacing(1, 2, 3, 4))
|
||||
|
||||
|
||||
def test_max():
|
||||
"""Check that max_width and max_height are respected."""
|
||||
styles = Styles()
|
||||
one = Fraction(1)
|
||||
|
||||
class TestWidget(Widget):
|
||||
def get_content_width(self, container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_content_height(self, container: Size, parent: Size, width: int) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
widget = TestWidget()
|
||||
styles = widget.styles
|
||||
|
||||
styles.width = 100
|
||||
styles.height = 80
|
||||
styles.max_width = 40
|
||||
styles.max_height = 30
|
||||
one = Fraction(1)
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(40, 30), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(40, 30), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(40), Fraction(30), Spacing(0, 0, 0, 0))
|
||||
|
||||
|
||||
def test_min():
|
||||
"""Check that min_width and min_height are respected."""
|
||||
styles = Styles()
|
||||
|
||||
one = Fraction(1)
|
||||
|
||||
class TestWidget(Widget):
|
||||
def get_content_width(self, container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_content_height(self, container: Size, parent: Size, width: int) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
widget = TestWidget()
|
||||
styles = widget.styles
|
||||
styles.width = 10
|
||||
styles.height = 5
|
||||
styles.min_width = 40
|
||||
styles.min_height = 30
|
||||
one = Fraction(1)
|
||||
|
||||
def get_auto_width(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
def get_auto_height(container: Size, parent: Size) -> int:
|
||||
assert False, "must not be called"
|
||||
|
||||
box_model = get_box_model(
|
||||
styles, Size(40, 30), Size(80, 24), one, one, get_auto_width, get_auto_height
|
||||
)
|
||||
box_model = widget._get_box_model(Size(40, 30), Size(80, 24), one, one)
|
||||
assert box_model == BoxModel(Fraction(40), Fraction(30), Spacing(0, 0, 0, 0))
|
||||
|
||||
Reference in New Issue
Block a user