fix for auto width and relative dimensions

This commit is contained in:
Will McGugan
2022-12-08 11:06:35 +00:00
parent b9cef559a3
commit bec01bba91
9 changed files with 563 additions and 373 deletions

View File

@@ -46,7 +46,8 @@ class Layout(ABC):
""" """
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int: def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
"""Get the width of the content. """Get the width of the content. In Horizontal layout, the content width of
a widget is the sum of the widths of its children.
Args: Args:
widget (Widget): The container widget. widget (Widget): The container widget.
@@ -56,19 +57,17 @@ class Layout(ABC):
Returns: Returns:
int: Width of the content. int: Width of the content.
""" """
width: int | None = None if not widget.children:
gutter_width = widget.gutter.width
for child in widget.displayed_children:
if not child.is_container:
child_width = (
child.get_content_width(container, viewport)
+ gutter_width
+ child.gutter.width
)
width = child_width if width is None else max(width, child_width)
if width is None:
width = 0 width = 0
else:
placements, _, _ = widget._arrange(Size(0, 0))
width = max(
[
placement.region.right + placement.margin.right
for placement in placements
],
default=0,
)
return width return width
def get_content_height( def get_content_height(
@@ -85,12 +84,16 @@ class Layout(ABC):
Returns: Returns:
int: Content height (in lines). int: Content height (in lines).
""" """
if not widget.displayed_children: if not widget.children:
height = 0 height = 0
else: else:
placements, *_ = widget._arrange(Size(width, container.height)) placements, _, _ = widget._arrange(Size(width, 0))
height = max( height = max(
placement.region.bottom + placement.margin.bottom [
for placement in placements placement.region.bottom + placement.margin.bottom
for placement in placements
],
default=0,
) )
return height return height

View File

@@ -1,3 +1,6 @@
Label {
width: 100%;
}
ColorButtons { ColorButtons {
dock: left; dock: left;

View File

@@ -190,7 +190,7 @@ LocationLink {
padding: 1 2; padding: 1 2;
background: $boost; background: $boost;
color: $text; color: $text;
box-sizing: content-box;
content-align: center middle; content-align: center middle;
} }

View File

@@ -59,25 +59,3 @@ class HorizontalLayout(Layout):
x = next_x + margin x = next_x + margin
return placements, set(displayed_children) return placements, set(displayed_children)
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
"""Get the width of the content. In Horizontal layout, the content width of
a widget is the sum of the widths of its children.
Args:
widget (Widget): The container widget.
container (Size): The container size.
viewport (Size): The viewport size.
Returns:
int: Width of the content.
"""
if not widget.displayed_children:
width = 0
else:
placements, *_ = widget._arrange(container)
width = max(
placement.region.right + placement.margin.right
for placement in placements
)
return width

View File

@@ -37,6 +37,7 @@ from rich.text import Text
from . import errors, events, messages from . import errors, events, messages
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
from ._arrange import DockArrangeResult, arrange from ._arrange import DockArrangeResult, arrange
from ._cache import LRUCache
from ._context import active_app from ._context import active_app
from ._easing import DEFAULT_SCROLL_EASING from ._easing import DEFAULT_SCROLL_EASING
from ._layout import Layout from ._layout import Layout
@@ -246,8 +247,8 @@ class Widget(DOMNode):
self._content_width_cache: tuple[object, int] = (None, 0) self._content_width_cache: tuple[object, int] = (None, 0)
self._content_height_cache: tuple[object, int] = (None, 0) self._content_height_cache: tuple[object, int] = (None, 0)
self._arrangement: DockArrangeResult | None = None self._arrangement_cache_updates: int = -1
self._arrangement_cache_key: tuple[int, Size] = (-1, Size()) self._arrangement_cache: LRUCache[Size, DockArrangeResult] = LRUCache(4)
self._styles_cache = StylesCache() self._styles_cache = StylesCache()
self._rich_style_cache: dict[str, tuple[Style, Style]] = {} self._rich_style_cache: dict[str, tuple[Style, Style]] = {}
@@ -457,21 +458,25 @@ class Widget(DOMNode):
Returns: Returns:
ArrangeResult: Widget locations. ArrangeResult: Widget locations.
""" """
assert self.is_container
arrange_cache_key = (self.children._updates, size) if self._arrangement_cache_updates != self.children._updates:
if ( self._arrangement_cache_updates = self.children._updates
self._arrangement is not None self._arrangement_cache.clear()
and arrange_cache_key == self._arrangement_cache_key
):
return self._arrangement
self._arrangement_cache_key = arrange_cache_key cached_arrangement = self._arrangement_cache.get(size, None)
self._arrangement = arrange(self, self.children, size, self.screen.size) if cached_arrangement is not None:
return self._arrangement return cached_arrangement
arrangement = self._arrangement_cache[size] = arrange(
self, self.children, size, self.screen.size
)
return arrangement
def _clear_arrangement_cache(self) -> None: def _clear_arrangement_cache(self) -> None:
"""Clear arrangement cache, forcing a new arrange operation.""" """Clear arrangement cache, forcing a new arrange operation."""
self._arrangement = None # self._arrangement = None
self._arrangement_cache.clear()
def _get_virtual_dom(self) -> Iterable[Widget]: def _get_virtual_dom(self) -> Iterable[Widget]:
"""Get widgets not part of the DOM. """Get widgets not part of the DOM.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,37 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal
from textual.widgets import Static
class HeightApp(App[None]):
CSS = """
Horizontal {
border: solid red;
height: auto;
}
Static {
border: solid green;
width: auto;
}
#fill_parent {
height: 100%;
}
#static {
height: 16;
}
"""
def compose(self) -> ComposeResult:
yield Horizontal(
Static("As tall as container", id="fill_parent"),
Static("This has default\nheight\nbut a\nfew lines"),
Static("I have a static height", id="static"),
)
if __name__ == "__main__":
HeightApp().run()

View File

@@ -16,7 +16,7 @@ class Body1(Vertical):
class Body2(Vertical): class Body2(Vertical):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Label("I'm sorry, Dave. I'm afraid I can't do that. " * 300) yield Label("My God! It's full of stars! " * 300)
class Good(Screen): class Good(Screen):
@@ -52,6 +52,10 @@ class Layers(App[None]):
background: red; background: red;
color: yellow; color: yellow;
} }
Body2 {
background: green;
}
""" """
SCREENS = {"good": Good, "bad": Bad} SCREENS = {"good": Good, "bad": Bad}

View File

@@ -139,15 +139,18 @@ def test_multiple_css(snap_compare):
def test_order_independence(snap_compare): def test_order_independence(snap_compare):
# Interaction between multiple CSS files and app-level/classvar CSS
assert snap_compare("snapshot_apps/order_independence.py") assert snap_compare("snapshot_apps/order_independence.py")
def test_order_independence_toggle(snap_compare): def test_order_independence_toggle(snap_compare):
# Interaction between multiple CSS files and app-level/classvar CSS
assert snap_compare("snapshot_apps/order_independence.py", press="t,_") assert snap_compare("snapshot_apps/order_independence.py", press="t,_")
def test_columns_height(snap_compare):
# Interaction with height auto, and relative heights to make columns
assert snap_compare("snapshot_apps/columns_height.py")
# --- Other --- # --- Other ---