From 51dbf16d4528eda6aa8f938ce4fca8f81eb61087 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 6 Oct 2025 09:21:49 +0100 Subject: [PATCH 01/11] faster layout --- src/textual/_arrange.py | 3 +- src/textual/_compositor.py | 3 +- src/textual/layouts/vertical.py | 23 +++++++++------ src/textual/messages.py | 4 +++ src/textual/reactive.py | 2 ++ src/textual/screen.py | 15 +++++++--- src/textual/widget.py | 51 ++++++++++++++++++++++++--------- src/textual/widgets/_button.py | 9 +++--- tests/test_box_model.py | 1 - 9 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index 6cdca4af3d..edfc8335e0 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -53,7 +53,6 @@ def arrange( Returns: Widget arrangement information. """ - placements: list[WidgetPlacement] = [] scroll_spacing = NULL_SPACING styles = widget.styles @@ -65,8 +64,10 @@ def arrange( for widgets in layers.values(): # Partition widgets into split widgets and non-split widgets + non_split_widgets, split_widgets = partition(_get_split, widgets) if split_widgets: + _split_placements, dock_region = _arrange_split_widgets( split_widgets, size, viewport ) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index f60d4c9b83..32192471ff 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -1248,8 +1248,7 @@ def update_widgets(self, widgets: set[Widget]) -> None: offset = region.offset intersection = clip.intersection for dirty_region in widget._exchange_repaint_regions(): - update_region = intersection(dirty_region.translate(offset)) - if update_region: + if update_region := intersection(dirty_region.translate(offset)): add_region(update_region) self._dirty_regions.update(regions) diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index da3462267d..8b795d469a 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -11,15 +11,19 @@ from textual.geometry import Spacing from textual.widget import Widget +from textual._profile import timer + class VerticalLayout(Layout): """Used to layout Widgets vertically on screen, from top to bottom.""" name = "vertical" + @timer("Vertical.arrange") def arrange( self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True ) -> ArrangeResult: + print(parent, size, greedy, len(children)) parent.pre_layout(self) placements: list[WidgetPlacement] = [] add_placement = placements.append @@ -50,15 +54,16 @@ def arrange( else: resolve_margin = Size(0, 0) - box_models = resolve_box_models( - [styles.height for styles in child_styles], - children, - size, - parent.app.size, - resolve_margin, - resolve_dimension="height", - greedy=greedy, - ) + with timer("resolve_box_models"): + box_models = resolve_box_models( + [styles.height for styles in child_styles], + children, + size, + parent.app.size, + resolve_margin, + resolve_dimension="height", + greedy=greedy, + ) margins = [ ( diff --git a/src/textual/messages.py b/src/textual/messages.py index dc0f6544a8..90d0c9fd82 100644 --- a/src/textual/messages.py +++ b/src/textual/messages.py @@ -52,6 +52,10 @@ def can_replace(self, message: Message) -> bool: class Layout(Message, verbose=True): """Sent by Textual when a layout is required.""" + def __init__(self, widget: Widget) -> None: + super().__init__() + self.widget = widget + def can_replace(self, message: Message) -> bool: return isinstance(message, Layout) diff --git a/src/textual/reactive.py b/src/textual/reactive.py index 57c0bf3ea1..f65a9f1a07 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -362,6 +362,8 @@ def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> Non # Refresh according to descriptor flags if self._layout or self._repaint or self._recompose: + if self._layout: + print(self, "layout", obj) obj.refresh( repaint=self._repaint, layout=self._layout, diff --git a/src/textual/screen.py b/src/textual/screen.py index d96dcbd909..05e18656e0 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -321,6 +321,9 @@ def __init__( self._css_update_count = -1 """Track updates to CSS.""" + self._layout_widgets: set[Widget] = set() + """Widgets whose layout may have changed.""" + @property def is_modal(self) -> bool: """Is the screen modal?""" @@ -486,11 +489,12 @@ def active_bindings(self) -> dict[str, ActiveBinding]: return bindings_map - def arrange(self, size: Size) -> DockArrangeResult: + def arrange(self, size: Size, _optimal: bool = False) -> DockArrangeResult: """Arrange children. Args: size: Size of container. + optimal: Ignored on screen. Returns: Widget locations. @@ -1270,7 +1274,7 @@ def _refresh_layout(self, size: Size | None = None, scroll: bool = False) -> Non ResizeEvent = events.Resize try: - if scroll: + if scroll and not self._layout_widgets: exposed_widgets = self._compositor.reflow_visible(self, size) if exposed_widgets: layers = self._compositor.layers @@ -1295,6 +1299,7 @@ def _refresh_layout(self, size: Size | None = None, scroll: bool = False) -> Non else: hidden, shown, resized = self._compositor.reflow(self, size) + self._layout_widgets.clear() Hide = events.Hide Show = events.Show @@ -1348,8 +1353,10 @@ async def _on_update(self, message: messages.Update) -> None: async def _on_layout(self, message: messages.Layout) -> None: message.stop() message.prevent_default() - self._layout_required = True - self.check_idle() + if message.widget not in self._layout_widgets: + self._layout_widgets.add(message.widget) + self._layout_required = True + self.check_idle() async def _on_update_scroll(self, message: messages.UpdateScroll) -> None: message.stop() diff --git a/src/textual/widget.py b/src/textual/widget.py index cf56e9536d..8ecfefdb35 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -60,7 +60,7 @@ from textual.actions import SkipAction from textual.await_remove import AwaitRemove from textual.box_model import BoxModel -from textual.cache import FIFOCache +from textual.cache import FIFOCache, LRUCache from textual.color import Color from textual.compose import compose from textual.content import Content, ContentType @@ -427,6 +427,7 @@ def __init__( self._size = _null_size self._container_size = _null_size self._layout_required = False + self._layout_updates = 0 self._repaint_required = False self._scroll_required = False self._recompose_required = False @@ -455,6 +456,8 @@ def __init__( # Regions which need to be transferred from cache to screen self._repaint_regions: set[Region] = set() + self._box_model_cache: LRUCache[object, BoxModel] = LRUCache(16) + # Cache the auto content dimensions self._content_width_cache: tuple[object, int] = (None, 0) self._content_height_cache: tuple[object, int] = (None, 0) @@ -1642,6 +1645,19 @@ def _get_box_model( Returns: The size and margin for this widget. """ + cache_key = ( + container, + viewport, + width_fraction, + height_fraction, + greedy, + constrain_width, + self._layout_updates, + self.styles._cache_key, + ) + if cached_box_model := self._box_model_cache.get(cache_key): + return cached_box_model + styles = self.styles is_border_box = styles.box_sizing == "border-box" gutter = styles.gutter # Padding plus border @@ -1750,6 +1766,7 @@ def _get_box_model( model = BoxModel( content_width + gutter.width, content_height + gutter.height, margin ) + self._box_model_cache[cache_key] = model return model def get_content_width(self, container: Size, viewport: Size) -> int: @@ -3999,10 +4016,10 @@ def _size_updated( if self._size != size: self._set_dirty() self._size = size - if layout: - self.virtual_size = virtual_size - else: - self.set_reactive(Widget.virtual_size, virtual_size) + # if layout: + # self.virtual_size = virtual_size + # else: + self.set_reactive(Widget.virtual_size, virtual_size) self._container_size = container_size if self.is_scrollable: self._scroll_update(virtual_size) @@ -4200,14 +4217,15 @@ def refresh( Returns: The `Widget` instance. """ - if layout: + if layout and not self._layout_required: self._layout_required = True - for ancestor in self.ancestors: - if not isinstance(ancestor, Widget): - break - ancestor._clear_arrangement_cache() - if not ancestor.styles.auto_dimensions: - break + self._layout_updates += 1 + # for ancestor in self.ancestors: + # if not isinstance(ancestor, Widget): + # break + # ancestor._clear_arrangement_cache() + # if not ancestor.styles.auto_dimensions: + # break if recompose: self._recompose_required = True @@ -4422,7 +4440,14 @@ def _check_refresh(self) -> None: screen.post_message(messages.Update(self)) if self._layout_required: self._layout_required = False - screen.post_message(messages.Layout()) + for ancestor in self.ancestors: + if not isinstance(ancestor, Widget): + break + ancestor._clear_arrangement_cache() + ancestor._layout_updates += 1 + if not ancestor.styles.auto_dimensions: + break + screen.post_message(messages.Layout(self)) def focus(self, scroll_visible: bool = True) -> Self: """Give focus to this widget. diff --git a/src/textual/widgets/_button.py b/src/textual/widgets/_button.py index 9d14a0557d..0dbb1f2667 100644 --- a/src/textual/widgets/_button.py +++ b/src/textual/widgets/_button.py @@ -228,7 +228,7 @@ class Button(Widget, can_focus=True): BINDINGS = [Binding("enter", "press", "Press button", show=False)] - label: reactive[ContentText] = reactive[ContentText](Content.empty) + label: reactive[ContentText] = reactive[ContentText](Content.empty()) """The text label that appears within the button.""" variant = reactive("default", init=False) @@ -293,11 +293,12 @@ def __init__( if label is None: label = self.css_identifier_styled - self.label = Content.from_text(label) self.variant = variant - self.action = action - self.compact = compact self.flat = flat + self.compact = compact + self.set_reactive(Button.label, Content.from_text(label)) + + self.action = action self.active_effect_duration = 0.2 """Amount of time in seconds the button 'press' animation lasts.""" diff --git a/tests/test_box_model.py b/tests/test_box_model.py index d775dc9be9..5a4b1790ae 100644 --- a/tests/test_box_model.py +++ b/tests/test_box_model.py @@ -133,7 +133,6 @@ def get_content_height(self, container: Size, parent: Size, width: int) -> int: styles.margin = 2 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 From 72d8049946e90b27465f76b3e80a48ce57c78fa9 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 6 Oct 2025 10:52:16 +0100 Subject: [PATCH 02/11] simplify --- src/textual/_styles_cache.py | 1 - src/textual/widget.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/textual/_styles_cache.py b/src/textual/_styles_cache.py index 646b56321a..e11acc0bbd 100644 --- a/src/textual/_styles_cache.py +++ b/src/textual/_styles_cache.py @@ -104,7 +104,6 @@ def render_widget(self, widget: Widget, crop: Region) -> list[Strip]: Returns: Rendered lines. """ - border_title = widget._border_title border_subtitle = widget._border_subtitle diff --git a/src/textual/widget.py b/src/textual/widget.py index 8ecfefdb35..e69ea3c45c 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -3627,7 +3627,7 @@ def scroll_visible( """ parent = self.parent if isinstance(parent, Widget): - if self.region: + if self._size: self.screen.scroll_to_widget( self, animate=animate, From 7e82efcb41f714e82388d64e814dd3e6c91c9f1b Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 6 Oct 2025 11:07:27 +0100 Subject: [PATCH 03/11] remove print --- src/textual/_arrange.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index edfc8335e0..0e8aec3742 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -53,6 +53,7 @@ def arrange( Returns: Widget arrangement information. """ + print("ARRANGE", widget) placements: list[WidgetPlacement] = [] scroll_spacing = NULL_SPACING styles = widget.styles From 46cc18879e1f553dfa9f10be9269b13c0bf10e30 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Mon, 6 Oct 2025 15:13:37 +0100 Subject: [PATCH 04/11] remove timer --- src/textual/layouts/vertical.py | 19 +++++++++---------- src/textual/widget.py | 2 +- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index 8b795d469a..d9846eefbd 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -54,16 +54,15 @@ def arrange( else: resolve_margin = Size(0, 0) - with timer("resolve_box_models"): - box_models = resolve_box_models( - [styles.height for styles in child_styles], - children, - size, - parent.app.size, - resolve_margin, - resolve_dimension="height", - greedy=greedy, - ) + box_models = resolve_box_models( + [styles.height for styles in child_styles], + children, + size, + parent.app.size, + resolve_margin, + resolve_dimension="height", + greedy=greedy, + ) margins = [ ( diff --git a/src/textual/widget.py b/src/textual/widget.py index e69ea3c45c..d460a76eda 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -1650,8 +1650,8 @@ def _get_box_model( viewport, width_fraction, height_fraction, - greedy, constrain_width, + greedy, self._layout_updates, self.styles._cache_key, ) From 665610ec0fc6f3618210c1b4107ba178006d0896 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 15:50:50 +0100 Subject: [PATCH 05/11] faster layout in stream --- src/textual/_arrange.py | 1 - src/textual/layouts/stream.py | 22 +++++++++++++++++++++- src/textual/layouts/vertical.py | 4 ---- src/textual/reactive.py | 2 -- src/textual/screen.py | 20 +++++++++++++++++--- 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index 0e8aec3742..edfc8335e0 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -53,7 +53,6 @@ def arrange( Returns: Widget arrangement information. """ - print("ARRANGE", widget) placements: list[WidgetPlacement] = [] scroll_spacing = NULL_SPACING styles = widget.styles diff --git a/src/textual/layouts/stream.py b/src/textual/layouts/stream.py index 4bf92d558b..e1c7201dfb 100644 --- a/src/textual/layouts/stream.py +++ b/src/textual/layouts/stream.py @@ -1,5 +1,6 @@ from __future__ import annotations +from itertools import zip_longest from typing import TYPE_CHECKING from textual.geometry import NULL_OFFSET, Region, Size @@ -30,6 +31,9 @@ class StreamLayout(Layout): name = "stream" + def __init__(self) -> None: + self._cache: dict[object, list[WidgetPlacement]] = {} + def arrange( self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True ) -> ArrangeResult: @@ -38,6 +42,10 @@ def arrange( return [] viewport = parent.app.viewport_size + cache_key = (size, viewport) + previous_results = self._cache.get(cache_key, None) or [] + layout_widgets = parent.screen._layout_widgets.get(parent, []) + _Region = Region _WidgetPlacement = WidgetPlacement @@ -48,7 +56,18 @@ def arrange( previous_margin = first_child_styles.margin.top null_offset = NULL_OFFSET - for widget in children: + pre_populate = bool(previous_results and layout_widgets) + for widget, placement in zip_longest(children, previous_results): + if pre_populate and placement is not None and widget == placement.widget: + if widget in layout_widgets: + pre_populate = False + else: + placements.append(placement) + y = placement.region.bottom + continue + if widget is None: + break + styles = widget.styles._base_styles margin = styles.margin gutter_width, gutter_height = styles.gutter.totals @@ -85,6 +104,7 @@ def arrange( ) y += height + self._cache[cache_key] = placements return placements def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int: diff --git a/src/textual/layouts/vertical.py b/src/textual/layouts/vertical.py index d9846eefbd..da3462267d 100644 --- a/src/textual/layouts/vertical.py +++ b/src/textual/layouts/vertical.py @@ -11,19 +11,15 @@ from textual.geometry import Spacing from textual.widget import Widget -from textual._profile import timer - class VerticalLayout(Layout): """Used to layout Widgets vertically on screen, from top to bottom.""" name = "vertical" - @timer("Vertical.arrange") def arrange( self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True ) -> ArrangeResult: - print(parent, size, greedy, len(children)) parent.pre_layout(self) placements: list[WidgetPlacement] = [] add_placement = placements.append diff --git a/src/textual/reactive.py b/src/textual/reactive.py index f65a9f1a07..57c0bf3ea1 100644 --- a/src/textual/reactive.py +++ b/src/textual/reactive.py @@ -362,8 +362,6 @@ def _set(self, obj: Reactable, value: ReactiveType, always: bool = False) -> Non # Refresh according to descriptor flags if self._layout or self._repaint or self._recompose: - if self._layout: - print(self, "layout", obj) obj.refresh( repaint=self._repaint, layout=self._layout, diff --git a/src/textual/screen.py b/src/textual/screen.py index 05e18656e0..4dce2371be 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -321,7 +321,7 @@ def __init__( self._css_update_count = -1 """Track updates to CSS.""" - self._layout_widgets: set[Widget] = set() + self._layout_widgets: dict[DOMNode, set[Widget]] = {} """Widgets whose layout may have changed.""" @property @@ -1353,8 +1353,22 @@ async def _on_update(self, message: messages.Update) -> None: async def _on_layout(self, message: messages.Layout) -> None: message.stop() message.prevent_default() - if message.widget not in self._layout_widgets: - self._layout_widgets.add(message.widget) + + layout_required = True + widget: DOMNode = message.widget + for ancestor in message.widget.ancestors: + if not isinstance(ancestor, Widget): + break + if ancestor not in self._layout_widgets: + self._layout_widgets[ancestor] = set() + # assert isinstance(widget, Widget) + self._layout_widgets[ancestor].add(widget) + layout_required = True + if not ancestor.styles.auto_dimensions: + break + widget = ancestor + + if layout_required: self._layout_required = True self.check_idle() From 528097e65b907ca1a4ed8561994612111c771772 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:04:57 +0100 Subject: [PATCH 06/11] simpler caching mechanism --- src/textual/_arrange.py | 2 -- src/textual/layouts/stream.py | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/textual/_arrange.py b/src/textual/_arrange.py index edfc8335e0..4cfd2448fb 100644 --- a/src/textual/_arrange.py +++ b/src/textual/_arrange.py @@ -64,10 +64,8 @@ def arrange( for widgets in layers.values(): # Partition widgets into split widgets and non-split widgets - non_split_widgets, split_widgets = partition(_get_split, widgets) if split_widgets: - _split_placements, dock_region = _arrange_split_widgets( split_widgets, size, viewport ) diff --git a/src/textual/layouts/stream.py b/src/textual/layouts/stream.py index e1c7201dfb..782beb3e3f 100644 --- a/src/textual/layouts/stream.py +++ b/src/textual/layouts/stream.py @@ -32,7 +32,9 @@ class StreamLayout(Layout): name = "stream" def __init__(self) -> None: - self._cache: dict[object, list[WidgetPlacement]] = {} + self._cached_placements: list[WidgetPlacement] | None = None + self._cached_width = 0 + super().__init__() def arrange( self, parent: Widget, children: list[Widget], size: Size, greedy: bool = True @@ -42,8 +44,10 @@ def arrange( return [] viewport = parent.app.viewport_size - cache_key = (size, viewport) - previous_results = self._cache.get(cache_key, None) or [] + if size.width != self._cached_width: + self._cached_placements = None + previous_results = self._cached_placements or [] + layout_widgets = parent.screen._layout_widgets.get(parent, []) _Region = Region @@ -58,12 +62,14 @@ def arrange( pre_populate = bool(previous_results and layout_widgets) for widget, placement in zip_longest(children, previous_results): - if pre_populate and placement is not None and widget == placement.widget: + if pre_populate and placement is not None and widget is placement.widget: if widget in layout_widgets: pre_populate = False else: placements.append(placement) y = placement.region.bottom + styles = widget.styles._base_styles + previous_margin = styles.margin.bottom continue if widget is None: break @@ -104,7 +110,8 @@ def arrange( ) y += height - self._cache[cache_key] = placements + self._cached_width = size.width + self._cached_placements = placements return placements def get_content_width(self, widget: Widget, container: Size, viewport: Size) -> int: From 0f20940de8d2116bfdb5bf6116f4b6e18a3e2e80 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:12:45 +0100 Subject: [PATCH 07/11] reduce layouts --- src/textual/screen.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/screen.py b/src/textual/screen.py index 4dce2371be..9f07ddc8b5 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -1354,16 +1354,16 @@ async def _on_layout(self, message: messages.Layout) -> None: message.stop() message.prevent_default() - layout_required = True + layout_required = False widget: DOMNode = message.widget for ancestor in message.widget.ancestors: if not isinstance(ancestor, Widget): break if ancestor not in self._layout_widgets: self._layout_widgets[ancestor] = set() - # assert isinstance(widget, Widget) - self._layout_widgets[ancestor].add(widget) - layout_required = True + if widget not in self._layout_widgets: + self._layout_widgets[ancestor].add(widget) + layout_required = True if not ancestor.styles.auto_dimensions: break widget = ancestor From 02a7b7bb3051bd45ebb10f8fcec6b27d09c591cd Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:16:17 +0100 Subject: [PATCH 08/11] reduce layout --- src/textual/screen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/textual/screen.py b/src/textual/screen.py index 9f07ddc8b5..c130af0a1a 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -1368,7 +1368,7 @@ async def _on_layout(self, message: messages.Layout) -> None: break widget = ancestor - if layout_required: + if layout_required and not self._layout_required: self._layout_required = True self.check_idle() From b6da06dbe110899b256dc22dc89652341605007d Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:33:09 +0100 Subject: [PATCH 09/11] snapshot fix? --- tests/snapshot_tests/test_snapshots.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 86cf17914a..1ace31a82c 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -807,7 +807,9 @@ def test_remove_with_auto_height(snap_compare): def test_auto_table(snap_compare): - assert snap_compare(SNAPSHOT_APPS_DIR / "auto-table.py", terminal_size=(120, 40)) + assert snap_compare( + SNAPSHOT_APPS_DIR / "auto-table.py", terminal_size=(120, 40), press=["wait:100"] + ) def test_table_markup(snap_compare): From 6a8c6cde261d0d3c345325d85b905a292ac139ef Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:45:48 +0100 Subject: [PATCH 10/11] remove comments --- src/textual/widget.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/textual/widget.py b/src/textual/widget.py index d460a76eda..316a57d569 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4220,12 +4220,6 @@ def refresh( if layout and not self._layout_required: self._layout_required = True self._layout_updates += 1 - # for ancestor in self.ancestors: - # if not isinstance(ancestor, Widget): - # break - # ancestor._clear_arrangement_cache() - # if not ancestor.styles.auto_dimensions: - # break if recompose: self._recompose_required = True From 1e047c767818a664564501f0d91fe99edfb3379e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 8 Oct 2025 16:48:02 +0100 Subject: [PATCH 11/11] restore layout behavior --- src/textual/widget.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/textual/widget.py b/src/textual/widget.py index 316a57d569..9382a25dc5 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -4016,10 +4016,10 @@ def _size_updated( if self._size != size: self._set_dirty() self._size = size - # if layout: - # self.virtual_size = virtual_size - # else: - self.set_reactive(Widget.virtual_size, virtual_size) + if layout: + self.virtual_size = virtual_size + else: + self.set_reactive(Widget.virtual_size, virtual_size) self._container_size = container_size if self.is_scrollable: self._scroll_update(virtual_size)