diff --git a/CHANGELOG.md b/CHANGELOG.md index 26b46f0b9e..413cb354a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Changed signature of Driver. Technically a breaking change, but unlikely to affect anyone. - Breaking change: Timer.start is now private, and returns No - A clicked tab will now be scrolled to the center of its tab container https://github.com/Textualize/textual/pull/2276 +- Style updates are now done immediately rather than on_idle https://github.com/Textualize/textual/pull/2304 - `ButtonVariant` is now exported from `textual.widgets.button` https://github.com/Textualize/textual/issues/2264 ### Added @@ -28,9 +29,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Fixed order styles are applied in DataTable - allows combining of renderable styles and component classes https://github.com/Textualize/textual/pull/2272 - Fix empty ListView preventing bindings from firing https://github.com/Textualize/textual/pull/2281 +- Fix `get_component_styles` returning incorrect values on first call when combined with pseudoclasses https://github.com/Textualize/textual/pull/2304 - Fixed `active_message_pump.get` sometimes resulting in a `LookupError` https://github.com/Textualize/textual/issues/2301 - ## [0.19.1] - 2023-04-10 ### Fixed diff --git a/src/textual/_cache.py b/src/textual/_cache.py index b808a97a09..3b7fb8f72d 100644 --- a/src/textual/_cache.py +++ b/src/textual/_cache.py @@ -72,9 +72,7 @@ def __len__(self) -> int: return len(self._cache) def __repr__(self) -> str: - return ( - f"" - ) + return f"" def grow(self, maxsize: int) -> None: """Grow the maximum size to at least `maxsize` elements. diff --git a/src/textual/app.py b/src/textual/app.py index 4ba9956cf7..d555481b0c 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -322,7 +322,6 @@ def __init__( self.design = DEFAULT_COLORS self.stylesheet = Stylesheet(variables=self.get_css_variables()) - self._require_stylesheet_update: set[DOMNode] = set() css_path = css_path or self.CSS_PATH if css_path is not None: @@ -1229,13 +1228,15 @@ def get_child_by_type(self, expect_type: type[ExpectType]) -> ExpectType: return self.screen.get_child_by_type(expect_type) def update_styles(self, node: DOMNode | None = None) -> None: - """Request update of styles. + """Immediately update the styles of this node and all descendant nodes. Should be called whenever CSS classes / pseudo classes change. - + For example, when you hover over a button, the :hover pseudo class + will be added, and this method is called to apply the corresponding + :hover styles. """ - self._require_stylesheet_update.add(self.screen if node is None else node) - self.check_idle() + descendants = node.walk_children(with_self=True) + self.stylesheet.update_nodes(descendants, animate=True) def mount( self, @@ -1773,14 +1774,6 @@ async def _on_compose(self) -> None: def _on_idle(self) -> None: """Perform actions when there are no messages in the queue.""" - if self._require_stylesheet_update and not self._batch_count: - nodes: set[DOMNode] = { - child - for node in self._require_stylesheet_update - for child in node.walk_children(with_self=True) - } - self._require_stylesheet_update.clear() - self.stylesheet.update_nodes(nodes, animate=True) def _register_child( self, parent: DOMNode, child: Widget, before: int | None, after: int | None diff --git a/src/textual/css/stylesheet.py b/src/textual/css/stylesheet.py index 7a48786de0..e18265d922 100644 --- a/src/textual/css/stylesheet.py +++ b/src/textual/css/stylesheet.py @@ -530,7 +530,6 @@ def update_nodes(self, nodes: Iterable[DOMNode], animate: bool = False) -> None: nodes: Nodes to update. animate: Enable CSS animation. """ - rules_map = self.rules_map apply = self.apply diff --git a/src/textual/devtools/service.py b/src/textual/devtools/service.py index f3c3f19f77..f1221196a4 100644 --- a/src/textual/devtools/service.py +++ b/src/textual/devtools/service.py @@ -252,6 +252,7 @@ async def run(self) -> WebSocketResponse: ): await self.incoming_queue.put(message) elif websocket_message.type == WSMsgType.ERROR: + self.service.console.print(websocket_message.data) self.service.console.print( DevConsoleNotice("Websocket error occurred", level="error") ) diff --git a/src/textual/widgets/_data_table.py b/src/textual/widgets/_data_table.py index 92d50b1d71..efbdc65e4a 100644 --- a/src/textual/widgets/_data_table.py +++ b/src/textual/widgets/_data_table.py @@ -269,7 +269,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True): background: $primary 10%; } - DataTable > .datatable--cursor { + DataTable > .datatable--cursor { background: $secondary; color: $text; } diff --git a/tests/test_app.py b/tests/test_app.py index 221286f116..e529cfbd7a 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -1,4 +1,5 @@ -from textual.app import App +from textual.app import App, ComposeResult +from textual.widgets import Button def test_batch_update(): @@ -15,3 +16,23 @@ def test_batch_update(): assert app._batch_count == 1 # Exiting decrements assert app._batch_count == 0 # Back to zero + + +class MyApp(App): + def compose(self) -> ComposeResult: + yield Button("Click me!") + + +async def test_hover_update_styles(): + app = MyApp() + async with app.run_test() as pilot: + button = app.query_one(Button) + assert button.pseudo_classes == {"enabled"} + + # Take note of the initial background colour + initial_background = button.styles.background + await pilot.hover(Button) + + # We've hovered, so ensure the pseudoclass is present and background changed + assert button.pseudo_classes == {"enabled", "hover"} + assert button.styles.background != initial_background diff --git a/tests/test_cache.py b/tests/test_cache.py index 384eccb236..1b00d28592 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -8,7 +8,7 @@ def test_lru_cache(): cache = LRUCache(3) - assert str(cache) == "" + assert str(cache) == "" # insert some values cache["foo"] = 1 @@ -65,7 +65,7 @@ def test_lru_cache_hits(): assert cache.hits == 3 assert cache.misses == 2 - assert str(cache) == "" + assert str(cache) == "" def test_lru_cache_get():