diff --git a/src/textual/_layout.py b/src/textual/_layout.py
index c263c13ee..e7bd0bf4b 100644
--- a/src/textual/_layout.py
+++ b/src/textual/_layout.py
@@ -46,7 +46,8 @@ class Layout(ABC):
"""
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:
widget (Widget): The container widget.
@@ -56,19 +57,17 @@ class Layout(ABC):
Returns:
int: Width of the content.
"""
- width: int | None = None
- 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:
+ if not widget.children:
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
def get_content_height(
@@ -85,12 +84,16 @@ class Layout(ABC):
Returns:
int: Content height (in lines).
"""
- if not widget.displayed_children:
+ if not widget.children:
height = 0
else:
- placements, *_ = widget._arrange(Size(width, container.height))
+ placements, _, _ = widget._arrange(Size(width, 0))
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
diff --git a/src/textual/cli/previews/colors.css b/src/textual/cli/previews/colors.css
index 657a8aabb..caa950430 100644
--- a/src/textual/cli/previews/colors.css
+++ b/src/textual/cli/previews/colors.css
@@ -1,3 +1,6 @@
+Label {
+ width: 100%;
+}
ColorButtons {
dock: left;
diff --git a/src/textual/demo.css b/src/textual/demo.css
index c6b7fecad..620cae5f0 100644
--- a/src/textual/demo.css
+++ b/src/textual/demo.css
@@ -190,7 +190,7 @@ LocationLink {
padding: 1 2;
background: $boost;
color: $text;
-
+ box-sizing: content-box;
content-align: center middle;
}
diff --git a/src/textual/layouts/horizontal.py b/src/textual/layouts/horizontal.py
index 08d5f7077..ac643663e 100644
--- a/src/textual/layouts/horizontal.py
+++ b/src/textual/layouts/horizontal.py
@@ -59,25 +59,3 @@ class HorizontalLayout(Layout):
x = next_x + margin
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
diff --git a/src/textual/widget.py b/src/textual/widget.py
index 4011925f7..4a68ce032 100644
--- a/src/textual/widget.py
+++ b/src/textual/widget.py
@@ -37,6 +37,7 @@ from rich.text import Text
from . import errors, events, messages
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
from ._arrange import DockArrangeResult, arrange
+from ._cache import LRUCache
from ._context import active_app
from ._easing import DEFAULT_SCROLL_EASING
from ._layout import Layout
@@ -246,8 +247,8 @@ class Widget(DOMNode):
self._content_width_cache: tuple[object, int] = (None, 0)
self._content_height_cache: tuple[object, int] = (None, 0)
- self._arrangement: DockArrangeResult | None = None
- self._arrangement_cache_key: tuple[int, Size] = (-1, Size())
+ self._arrangement_cache_updates: int = -1
+ self._arrangement_cache: LRUCache[Size, DockArrangeResult] = LRUCache(4)
self._styles_cache = StylesCache()
self._rich_style_cache: dict[str, tuple[Style, Style]] = {}
@@ -457,21 +458,25 @@ class Widget(DOMNode):
Returns:
ArrangeResult: Widget locations.
"""
+ assert self.is_container
- arrange_cache_key = (self.children._updates, size)
- if (
- self._arrangement is not None
- and arrange_cache_key == self._arrangement_cache_key
- ):
- return self._arrangement
+ if self._arrangement_cache_updates != self.children._updates:
+ self._arrangement_cache_updates = self.children._updates
+ self._arrangement_cache.clear()
- self._arrangement_cache_key = arrange_cache_key
- self._arrangement = arrange(self, self.children, size, self.screen.size)
- return self._arrangement
+ cached_arrangement = self._arrangement_cache.get(size, None)
+ if cached_arrangement is not None:
+ return cached_arrangement
+
+ arrangement = self._arrangement_cache[size] = arrange(
+ self, self.children, size, self.screen.size
+ )
+ return arrangement
def _clear_arrangement_cache(self) -> None:
"""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]:
"""Get widgets not part of the DOM.
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index d950c09a7..a7edd1f43 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -344,6 +344,163 @@
'''
# ---
+# name: test_columns_height
+ '''
+
+
+ '''
+# ---
# name: test_css_property[align.py]
'''