From f145f282e4f84884474cb73796ea406e4b061a5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:40:37 +0000 Subject: [PATCH 1/5] Typing fixes. --- src/textual/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/textual/app.py b/src/textual/app.py index 59332971aa..612a47415a 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -428,7 +428,7 @@ def __init__( self._workers = WorkerManager(self) self.error_console = Console(markup=False, stderr=True) self.driver_class = driver_class or self.get_driver_class() - self._screen_stacks: dict[str, list[Screen]] = {"_default": []} + self._screen_stacks: dict[str, list[Screen[Any]]] = {"_default": []} """A stack of screens per mode.""" self._current_mode: str = "_default" """The current mode the app is in.""" @@ -722,7 +722,7 @@ def is_headless(self) -> bool: return False if self._driver is None else self._driver.is_headless @property - def screen_stack(self) -> Sequence[Screen]: + def screen_stack(self) -> Sequence[Screen[Any]]: """A snapshot of the current screen stack. Returns: @@ -731,7 +731,7 @@ def screen_stack(self) -> Sequence[Screen]: return self._screen_stacks[self._current_mode].copy() @property - def _screen_stack(self) -> list[Screen]: + def _screen_stack(self) -> list[Screen[Any]]: """A reference to the current screen stack. Note: From ea5cd4fc2d8e27e292380e6c11beb1d2621b3dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 8 Jan 2024 15:40:53 +0000 Subject: [PATCH 2/5] Apply CSS changes to all screens on stack. --- src/textual/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/textual/app.py b/src/textual/app.py index 612a47415a..3f90639dd5 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1494,7 +1494,9 @@ async def _on_css_change(self) -> None: self._css_has_errors = False self.stylesheet = stylesheet self.stylesheet.update(self) - self.screen.refresh(layout=True) + for screen in self.screen_stack: + self.stylesheet.update(screen) + # self.screen.refresh(layout=True) def render(self) -> RenderableType: return Blank(self.styles.background) From 0926caf7cec0196f170f892588fb4e2f5a1110a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:16:50 +0000 Subject: [PATCH 3/5] Add regression test for #3931. --- tests/css/css_reloading.tcss | 1 + tests/css/test_css_reloading.py | 65 +++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/css/css_reloading.tcss create mode 100644 tests/css/test_css_reloading.py diff --git a/tests/css/css_reloading.tcss b/tests/css/css_reloading.tcss new file mode 100644 index 0000000000..12579d4579 --- /dev/null +++ b/tests/css/css_reloading.tcss @@ -0,0 +1 @@ +/* This file has no rules intentionally. */ diff --git a/tests/css/test_css_reloading.py b/tests/css/test_css_reloading.py new file mode 100644 index 0000000000..8da5312dfe --- /dev/null +++ b/tests/css/test_css_reloading.py @@ -0,0 +1,65 @@ +""" +Regression test for https://github.com/Textualize/textual/issues/3931 +""" + +from pathlib import Path + +from textual.app import App, ComposeResult +from textual.screen import Screen +from textual.widgets import Label + +CSS_PATH = (Path(__file__) / "../css_reloading.tcss").resolve() + +Path(CSS_PATH).write_text( + """\ +Label { + height: 5; + border: panel white; +} +""" +) + + +class BaseScreen(Screen[None]): + def compose(self) -> ComposeResult: + yield Label("I am the base screen") + + +class TopScreen(Screen[None]): + DEFAULT_CSS = """ + TopScreen { + opacity: 1; + background: green 0%; + } + """ + + +class MyApp(App[None]): + CSS_PATH = CSS_PATH + + def on_mount(self) -> None: + self.push_screen(BaseScreen()) + self.push_screen(TopScreen()) + + +async def test_css_reloading_applies_to_non_top_screen(monkeypatch) -> None: # type: ignore + """Regression test for https://github.com/Textualize/textual/issues/2063.""" + + monkeypatch.setenv( + "TEXTUAL", "debug" + ) # This will make sure we create a file monitor. + + app = MyApp() + async with app.run_test() as pilot: + await pilot.pause() + first_label = pilot.app.screen_stack[-2].query(Label).first() + # Sanity check. + assert first_label.styles.height is not None + assert first_label.styles.height.value == 5 + + # Clear the CSS from the file. + Path(CSS_PATH).write_text("/* This file has no rules intentionally. */\n") + await pilot.app._on_css_change() + # Height should fall back to 1. + assert first_label.styles.height is not None + assert first_label.styles.height.value == 1 From fea45fe50d89e594f4e534ce53529e7a93828b0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:17:36 +0000 Subject: [PATCH 4/5] Changelog. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 178b1b74a4..29c4474132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Parameter `animate` from `DataTable.move_cursor` was being ignored https://github.com/Textualize/textual/issues/3840 - Fixed a crash if `DirectoryTree.show_root` was set before the DOM was fully available https://github.com/Textualize/textual/issues/2363 +- Live reloading of TCSS wouldn't apply CSS changes to screens under the top screen of the stack https://github.com/Textualize/textual/issues/3931 ## [0.47.1] - 2023-01-05 From 15666768dc1f0e6dacec63291e1f84ce92e8d369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20Gir=C3=A3o=20Serr=C3=A3o?= <5621605+rodrigogiraoserrao@users.noreply.github.com> Date: Mon, 8 Jan 2024 17:27:47 +0000 Subject: [PATCH 5/5] Delete commented out line. --- src/textual/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/textual/app.py b/src/textual/app.py index 3f90639dd5..4f679c4787 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -1496,7 +1496,6 @@ async def _on_css_change(self) -> None: self.stylesheet.update(self) for screen in self.screen_stack: self.stylesheet.update(screen) - # self.screen.refresh(layout=True) def render(self) -> RenderableType: return Blank(self.styles.background)