Skip to content

Commit

Permalink
Merge pull request #3981 from Textualize/apply-css-changes-screen-stack
Browse files Browse the repository at this point in the history
Apply css changes screen stack
  • Loading branch information
rodrigogiraoserrao authored Jan 9, 2024
2 parents b4ba573 + 1566676 commit c931d52
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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
Expand Down
9 changes: 5 additions & 4 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -1494,7 +1494,8 @@ 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)

def render(self) -> RenderableType:
return Blank(self.styles.background)
Expand Down
1 change: 1 addition & 0 deletions tests/css/css_reloading.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* This file has no rules intentionally. */
65 changes: 65 additions & 0 deletions tests/css/test_css_reloading.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit c931d52

Please sign in to comment.