diff --git a/src/textual/widget.py b/src/textual/widget.py index 68c88b0f39..40679c959e 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -456,6 +456,9 @@ def __init__( """A dict that is refreshed when the widget is resized / refreshed.""" self._visual_style: VisualStyle | None = None + """Cached style of visual.""" + self._visual_style_cache_key: int = -1 + """Cache busting integer.""" self._render_cache = _RenderCache(_null_size, []) # Regions which need to be updated (in Widget) @@ -4127,7 +4130,12 @@ def _scroll_update(self, virtual_size: Size) -> None: @property def visual_style(self) -> VisualStyle: - if self._visual_style is None: + """The widget's current style.""" + if ( + self._visual_style is None + or self._visual_style_cache_key != self.styles._cache_key + ): + self._visual_style_cache_key = self.styles._cache_key background = Color(0, 0, 0, 0) color = Color(255, 255, 255, 0) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots/test_visual_style_caching.svg b/tests/snapshot_tests/__snapshots__/test_snapshots/test_visual_style_caching.svg new file mode 100644 index 0000000000..ce5021fe59 --- /dev/null +++ b/tests/snapshot_tests/__snapshots__/test_snapshots/test_visual_style_caching.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WatchApp + + + + + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ +Enter a color +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ +▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + +▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + + + diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index c84b34e467..b706e5d3dc 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -12,6 +12,7 @@ from textual._on import on from textual.app import App, ComposeResult from textual.binding import Binding +from textual.color import Color, ColorParseError from textual.command import SimpleCommand from textual.containers import ( Center, @@ -26,7 +27,7 @@ ) from textual.content import Content from textual.pilot import Pilot -from textual.reactive import var +from textual.reactive import reactive, var from textual.renderables.gradient import LinearGradient from textual.screen import ModalScreen, Screen from textual.widgets import ( @@ -4804,3 +4805,69 @@ async def run_before(pilot: Pilot) -> None: await pilot.press("ctrl+v") assert snap_compare(TextAreaApp(), run_before=run_before) + + +def test_visual_style_caching(snap_compare) -> None: + """Regression test for https://github.com/Textualize/textual/issues/6322 + + Check that `visual_style` isn't cached when it shouldn't be. + + You should see a solid red panel on the left, and a solid green panel on the right. + + """ + + class WatchApp(App): + CSS = """ +Input { + dock: top; + margin-top: 1; +} + +#colors { + grid-size: 2 1; + grid-gutter: 2 4; + grid-columns: 1fr; + margin: 0 1; +} + +#old { + height: 100%; + border: wide $secondary; +} + +#new { + height: 100%; + border: wide $secondary; +} + +""" + + color = reactive(Color.parse("transparent")) + + def compose(self) -> ComposeResult: + yield Input(placeholder="Enter a color") + yield Grid(Static(id="old"), Static(id="new"), id="colors") + + def watch_color(self, old_color: Color, new_color: Color) -> None: + self.query_one("#old").styles.background = old_color + self.query_one("#new").styles.background = new_color + + def on_input_submitted(self, event: Input.Submitted) -> None: + try: + input_color = Color.parse(event.value) + except ColorParseError: + pass + else: + self.query_one(Input).value = "" + self.color = input_color + + async def run_before(pilot: Pilot) -> None: + await pilot.pause() + await pilot.press(*"red") + await pilot.pause() + await pilot.press("enter") + await pilot.pause() + await pilot.press(*"green") + await pilot.press("enter") + + assert snap_compare(WatchApp(), run_before=run_before)