Skip to content

Commit bec01bb

Browse files
committed
fix for auto width and relative dimensions
1 parent b9cef55 commit bec01bb

File tree

9 files changed

+439
-249
lines changed

9 files changed

+439
-249
lines changed

src/textual/_layout.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def arrange(
4646
"""
4747

4848
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
49-
"""Get the width of the content.
49+
"""Get the width of the content. In Horizontal layout, the content width of
50+
a widget is the sum of the widths of its children.
5051
5152
Args:
5253
widget (Widget): The container widget.
@@ -56,19 +57,17 @@ def get_content_width(self, widget: Widget, container: Size, viewport: Size) ->
5657
Returns:
5758
int: Width of the content.
5859
"""
59-
width: int | None = None
60-
gutter_width = widget.gutter.width
61-
for child in widget.displayed_children:
62-
if not child.is_container:
63-
child_width = (
64-
child.get_content_width(container, viewport)
65-
+ gutter_width
66-
+ child.gutter.width
67-
)
68-
width = child_width if width is None else max(width, child_width)
69-
if width is None:
60+
if not widget.children:
7061
width = 0
71-
62+
else:
63+
placements, _, _ = widget._arrange(Size(0, 0))
64+
width = max(
65+
[
66+
placement.region.right + placement.margin.right
67+
for placement in placements
68+
],
69+
default=0,
70+
)
7271
return width
7372

7473
def get_content_height(
@@ -85,12 +84,16 @@ def get_content_height(
8584
Returns:
8685
int: Content height (in lines).
8786
"""
88-
if not widget.displayed_children:
87+
if not widget.children:
8988
height = 0
9089
else:
91-
placements, *_ = widget._arrange(Size(width, container.height))
90+
placements, _, _ = widget._arrange(Size(width, 0))
9291
height = max(
93-
placement.region.bottom + placement.margin.bottom
94-
for placement in placements
92+
[
93+
placement.region.bottom + placement.margin.bottom
94+
for placement in placements
95+
],
96+
default=0,
9597
)
98+
9699
return height

src/textual/cli/previews/colors.css

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
Label {
2+
width: 100%;
3+
}
14

25
ColorButtons {
36
dock: left;

src/textual/demo.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ LocationLink {
190190
padding: 1 2;
191191
background: $boost;
192192
color: $text;
193-
193+
box-sizing: content-box;
194194
content-align: center middle;
195195
}
196196

src/textual/layouts/horizontal.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,25 +59,3 @@ def arrange(
5959
x = next_x + margin
6060

6161
return placements, set(displayed_children)
62-
63-
def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int:
64-
"""Get the width of the content. In Horizontal layout, the content width of
65-
a widget is the sum of the widths of its children.
66-
67-
Args:
68-
widget (Widget): The container widget.
69-
container (Size): The container size.
70-
viewport (Size): The viewport size.
71-
72-
Returns:
73-
int: Width of the content.
74-
"""
75-
if not widget.displayed_children:
76-
width = 0
77-
else:
78-
placements, *_ = widget._arrange(container)
79-
width = max(
80-
placement.region.right + placement.margin.right
81-
for placement in placements
82-
)
83-
return width

src/textual/widget.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from . import errors, events, messages
3838
from ._animator import DEFAULT_EASING, Animatable, BoundAnimator, EasingFunction
3939
from ._arrange import DockArrangeResult, arrange
40+
from ._cache import LRUCache
4041
from ._context import active_app
4142
from ._easing import DEFAULT_SCROLL_EASING
4243
from ._layout import Layout
@@ -246,8 +247,8 @@ def __init__(
246247
self._content_width_cache: tuple[object, int] = (None, 0)
247248
self._content_height_cache: tuple[object, int] = (None, 0)
248249

249-
self._arrangement: DockArrangeResult | None = None
250-
self._arrangement_cache_key: tuple[int, Size] = (-1, Size())
250+
self._arrangement_cache_updates: int = -1
251+
self._arrangement_cache: LRUCache[Size, DockArrangeResult] = LRUCache(4)
251252

252253
self._styles_cache = StylesCache()
253254
self._rich_style_cache: dict[str, tuple[Style, Style]] = {}
@@ -457,21 +458,25 @@ def _arrange(self, size: Size) -> DockArrangeResult:
457458
Returns:
458459
ArrangeResult: Widget locations.
459460
"""
461+
assert self.is_container
460462

461-
arrange_cache_key = (self.children._updates, size)
462-
if (
463-
self._arrangement is not None
464-
and arrange_cache_key == self._arrangement_cache_key
465-
):
466-
return self._arrangement
463+
if self._arrangement_cache_updates != self.children._updates:
464+
self._arrangement_cache_updates = self.children._updates
465+
self._arrangement_cache.clear()
467466

468-
self._arrangement_cache_key = arrange_cache_key
469-
self._arrangement = arrange(self, self.children, size, self.screen.size)
470-
return self._arrangement
467+
cached_arrangement = self._arrangement_cache.get(size, None)
468+
if cached_arrangement is not None:
469+
return cached_arrangement
470+
471+
arrangement = self._arrangement_cache[size] = arrange(
472+
self, self.children, size, self.screen.size
473+
)
474+
return arrangement
471475

472476
def _clear_arrangement_cache(self) -> None:
473477
"""Clear arrangement cache, forcing a new arrange operation."""
474-
self._arrangement = None
478+
# self._arrangement = None
479+
self._arrangement_cache.clear()
475480

476481
def _get_virtual_dom(self) -> Iterable[Widget]:
477482
"""Get widgets not part of the DOM.

tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Lines changed: 351 additions & 194 deletions
Large diffs are not rendered by default.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from textual.app import App, ComposeResult
2+
from textual.containers import Horizontal
3+
from textual.widgets import Static
4+
5+
6+
class HeightApp(App[None]):
7+
8+
CSS = """
9+
Horizontal {
10+
border: solid red;
11+
height: auto;
12+
}
13+
14+
Static {
15+
border: solid green;
16+
width: auto;
17+
}
18+
19+
#fill_parent {
20+
height: 100%;
21+
}
22+
23+
#static {
24+
height: 16;
25+
}
26+
"""
27+
28+
def compose(self) -> ComposeResult:
29+
yield Horizontal(
30+
Static("As tall as container", id="fill_parent"),
31+
Static("This has default\nheight\nbut a\nfew lines"),
32+
Static("I have a static height", id="static"),
33+
)
34+
35+
36+
if __name__ == "__main__":
37+
HeightApp().run()

tests/snapshot_tests/snapshot_apps/order_independence.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def compose(self) -> ComposeResult:
1616

1717
class Body2(Vertical):
1818
def compose(self) -> ComposeResult:
19-
yield Label("I'm sorry, Dave. I'm afraid I can't do that. " * 300)
19+
yield Label("My God! It's full of stars! " * 300)
2020

2121

2222
class Good(Screen):
@@ -52,6 +52,10 @@ class Layers(App[None]):
5252
background: red;
5353
color: yellow;
5454
}
55+
56+
Body2 {
57+
background: green;
58+
}
5559
"""
5660

5761
SCREENS = {"good": Good, "bad": Bad}

tests/snapshot_tests/test_snapshots.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,15 +139,18 @@ def test_multiple_css(snap_compare):
139139

140140

141141
def test_order_independence(snap_compare):
142-
# Interaction between multiple CSS files and app-level/classvar CSS
143142
assert snap_compare("snapshot_apps/order_independence.py")
144143

145144

146145
def test_order_independence_toggle(snap_compare):
147-
# Interaction between multiple CSS files and app-level/classvar CSS
148146
assert snap_compare("snapshot_apps/order_independence.py", press="t,_")
149147

150148

149+
def test_columns_height(snap_compare):
150+
# Interaction with height auto, and relative heights to make columns
151+
assert snap_compare("snapshot_apps/columns_height.py")
152+
153+
151154
# --- Other ---
152155

153156

0 commit comments

Comments
 (0)