From c025124dc7057ac2bc74278fb47b9b2d47f7ff41 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 24 Sep 2024 17:22:36 +0100 Subject: [PATCH 1/5] fix missing new widgets --- src/textual/_compositor.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 3aff169529..d46326c31d 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -309,6 +309,9 @@ def __init__(self) -> None: # Mapping of line numbers on to lists of widget and regions self._layers_visible: list[list[tuple[Widget, Region, Region]]] | None = None + # New widgets added between updates + self._new_widgets: set[Widget] = set() + def clear(self) -> None: """Remove all references to widgets (used when the screen closes).""" self._full_map.clear() @@ -387,7 +390,9 @@ def reflow(self, parent: Widget, size: Size) -> ReflowResult: new_widgets = map.keys() # Newly visible widgets - shown_widgets = new_widgets - old_widgets + shown_widgets = (new_widgets - old_widgets) | self._new_widgets + self._new_widgets.clear() + # Newly hidden widgets hidden_widgets = self.widgets - widgets @@ -419,7 +424,6 @@ def reflow(self, parent: Widget, size: Size) -> ReflowResult: for widget, (region, *_) in changes if (widget in common_widgets and old_map[widget].region[2:] != region[2:]) } - return ReflowResult( hidden=hidden_widgets, shown=shown_widgets, @@ -482,7 +486,9 @@ def full_map(self) -> CompositorMap: return {} if self._full_map_invalidated: self._full_map_invalidated = False - map, _widgets = self._arrange_root(self.root, self.size, visible_only=False) + old = self._full_map.keys() + map, widgets = self._arrange_root(self.root, self.size, visible_only=False) + self._new_widgets.update(map.keys() - old) self._full_map = map self._visible_widgets = None self._visible_map = None From 3632fef80a1e2a958c04710d9e1798c4691c0484 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 24 Sep 2024 17:31:50 +0100 Subject: [PATCH 2/5] snapshot --- src/textual/_compositor.py | 4 +- .../test_missing_new_widgets.svg | 155 ++++++++++++++++++ tests/snapshot_tests/test_snapshots.py | 33 +++- 3 files changed, 187 insertions(+), 5 deletions(-) create mode 100644 tests/snapshot_tests/__snapshots__/test_snapshots/test_missing_new_widgets.svg diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index d46326c31d..7ca7f18432 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -486,9 +486,9 @@ def full_map(self) -> CompositorMap: return {} if self._full_map_invalidated: self._full_map_invalidated = False - old = self._full_map.keys() + old_widgets = self._full_map.keys() map, widgets = self._arrange_root(self.root, self.size, visible_only=False) - self._new_widgets.update(map.keys() - old) + self._new_widgets.update(map.keys() - old_widgets) self._full_map = map self._visible_widgets = None self._visible_map = None diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_missing_new_widgets.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_missing_new_widgets.svg new file mode 100644 index 0000000000..c10f680564 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_missing_new_widgets.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MRE + + + + + + + + + + + + + + + + + + + + + + + + + + +╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍ +line #0                                                                        +line #1                                                                        +line #2                                                                        +line #3                                                                        +line #4                                                                        + z Console ^p palette + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index cc543a2b3a..b0b848e381 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -9,7 +9,7 @@ from textual import events from textual.app import App, ComposeResult from textual.binding import Binding -from textual.containers import Vertical +from textual.containers import Vertical, VerticalScroll from textual.pilot import Pilot from textual.screen import Screen from textual.widgets import ( @@ -24,8 +24,7 @@ OptionList, SelectionList, ) -from textual.widgets import Switch -from textual.widgets import Label +from textual.widgets import ProgressBar, Label, Switch from textual.widgets.text_area import BUILTIN_LANGUAGES, Selection, TextAreaTheme # These paths should be relative to THIS directory. @@ -1992,3 +1991,31 @@ def on_mount(self) -> None: app = DisabledApp() assert snap_compare(app) + + +def test_missing_new_widgets(snap_compare): + """Regression test for https://github.com/Textualize/textual/issues/5024""" + + class MRE(App): + BINDINGS = [("z", "toggle_console", "Console")] + CSS = """ + RichLog { border-top: dashed blue; height: 6; } + .hidden { display: none; } + """ + + def compose(self): + yield VerticalScroll() + yield ProgressBar( + classes="hidden" + ) # removing or displaying this widget prevents the bug + yield Footer() # clicking "Console" in the footer prevents the bug + yield RichLog(classes="hidden") + + def on_ready(self) -> None: + self.query_one(RichLog).write("\n".join(f"line #{i}" for i in range(5))) + + def action_toggle_console(self) -> None: + self.query_one(RichLog).toggle_class("hidden") + + app = MRE() + assert snap_compare(app, press=["space", "space", "z"]) From c6dd2ee3973759be30a82a3232935b07a88282e0 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 24 Sep 2024 17:34:25 +0100 Subject: [PATCH 3/5] simplify --- src/textual/_compositor.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 7ca7f18432..636db18581 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -486,9 +486,8 @@ def full_map(self) -> CompositorMap: return {} if self._full_map_invalidated: self._full_map_invalidated = False - old_widgets = self._full_map.keys() - map, widgets = self._arrange_root(self.root, self.size, visible_only=False) - self._new_widgets.update(map.keys() - old_widgets) + map, _widgets = self._arrange_root(self.root, self.size, visible_only=False) + self._new_widgets.update(map.keys() - self._full_map.keys()) self._full_map = map self._visible_widgets = None self._visible_map = None From b53fa7943a09ef690d3bc36bac33932c4271ff6e Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 24 Sep 2024 17:35:55 +0100 Subject: [PATCH 4/5] comment --- src/textual/_compositor.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/textual/_compositor.py b/src/textual/_compositor.py index 636db18581..8eb6d063b7 100644 --- a/src/textual/_compositor.py +++ b/src/textual/_compositor.py @@ -487,6 +487,7 @@ def full_map(self) -> CompositorMap: if self._full_map_invalidated: self._full_map_invalidated = False map, _widgets = self._arrange_root(self.root, self.size, visible_only=False) + # Update any widgets which became visible in the interim self._new_widgets.update(map.keys() - self._full_map.keys()) self._full_map = map self._visible_widgets = None From 702d33b34e7e3d583d72b94ac0b6f4fb73f08fba Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Tue, 24 Sep 2024 17:36:49 +0100 Subject: [PATCH 5/5] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8240a60ee2..4c8024f0ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Tree will no longer scroll the X axis when moving the cursor https://github.com/Textualize/textual/pull/5047 +### Fixed + +- Fixed widgets occasionally not getting Resize events https://github.com/Textualize/textual/pull/5048 + ## [0.80.1] - 2024-09-24 ### Fixed