Skip to content

Commit 496f8b4

Browse files
authored
Updating styles on demand instead of on_idle (#2304)
* Updating styles on demand instead of on_idle * Tidy up update_styles * Fix LRU cache tests * Remove some debugging code * Adding test for pseudoclass style update * Update changelog
1 parent 01d6717 commit 496f8b4

File tree

8 files changed

+35
-22
lines changed

8 files changed

+35
-22
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1212
- Changed signature of Driver. Technically a breaking change, but unlikely to affect anyone.
1313
- Breaking change: Timer.start is now private, and returns No
1414
- A clicked tab will now be scrolled to the center of its tab container https://github.com/Textualize/textual/pull/2276
15+
- Style updates are now done immediately rather than on_idle https://github.com/Textualize/textual/pull/2304
1516
- `ButtonVariant` is now exported from `textual.widgets.button` https://github.com/Textualize/textual/issues/2264
1617

1718
### Added
@@ -28,9 +29,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2829

2930
- Fixed order styles are applied in DataTable - allows combining of renderable styles and component classes https://github.com/Textualize/textual/pull/2272
3031
- Fix empty ListView preventing bindings from firing https://github.com/Textualize/textual/pull/2281
32+
- Fix `get_component_styles` returning incorrect values on first call when combined with pseudoclasses https://github.com/Textualize/textual/pull/2304
3133
- Fixed `active_message_pump.get` sometimes resulting in a `LookupError` https://github.com/Textualize/textual/issues/2301
3234

33-
3435
## [0.19.1] - 2023-04-10
3536

3637
### Fixed

src/textual/_cache.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,7 @@ def __len__(self) -> int:
7272
return len(self._cache)
7373

7474
def __repr__(self) -> str:
75-
return (
76-
f"<LRUCache maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
77-
)
75+
return f"<LRUCache size={len(self)} maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
7876

7977
def grow(self, maxsize: int) -> None:
8078
"""Grow the maximum size to at least `maxsize` elements.

src/textual/app.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,6 @@ def __init__(
322322
self.design = DEFAULT_COLORS
323323

324324
self.stylesheet = Stylesheet(variables=self.get_css_variables())
325-
self._require_stylesheet_update: set[DOMNode] = set()
326325

327326
css_path = css_path or self.CSS_PATH
328327
if css_path is not None:
@@ -1229,13 +1228,15 @@ def get_child_by_type(self, expect_type: type[ExpectType]) -> ExpectType:
12291228
return self.screen.get_child_by_type(expect_type)
12301229

12311230
def update_styles(self, node: DOMNode | None = None) -> None:
1232-
"""Request update of styles.
1231+
"""Immediately update the styles of this node and all descendant nodes.
12331232
12341233
Should be called whenever CSS classes / pseudo classes change.
1235-
1234+
For example, when you hover over a button, the :hover pseudo class
1235+
will be added, and this method is called to apply the corresponding
1236+
:hover styles.
12361237
"""
1237-
self._require_stylesheet_update.add(self.screen if node is None else node)
1238-
self.check_idle()
1238+
descendants = node.walk_children(with_self=True)
1239+
self.stylesheet.update_nodes(descendants, animate=True)
12391240

12401241
def mount(
12411242
self,
@@ -1773,14 +1774,6 @@ async def _on_compose(self) -> None:
17731774

17741775
def _on_idle(self) -> None:
17751776
"""Perform actions when there are no messages in the queue."""
1776-
if self._require_stylesheet_update and not self._batch_count:
1777-
nodes: set[DOMNode] = {
1778-
child
1779-
for node in self._require_stylesheet_update
1780-
for child in node.walk_children(with_self=True)
1781-
}
1782-
self._require_stylesheet_update.clear()
1783-
self.stylesheet.update_nodes(nodes, animate=True)
17841777

17851778
def _register_child(
17861779
self, parent: DOMNode, child: Widget, before: int | None, after: int | None

src/textual/css/stylesheet.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,6 @@ def update_nodes(self, nodes: Iterable[DOMNode], animate: bool = False) -> None:
530530
nodes: Nodes to update.
531531
animate: Enable CSS animation.
532532
"""
533-
534533
rules_map = self.rules_map
535534
apply = self.apply
536535

src/textual/devtools/service.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ async def run(self) -> WebSocketResponse:
252252
):
253253
await self.incoming_queue.put(message)
254254
elif websocket_message.type == WSMsgType.ERROR:
255+
self.service.console.print(websocket_message.data)
255256
self.service.console.print(
256257
DevConsoleNotice("Websocket error occurred", level="error")
257258
)

src/textual/widgets/_data_table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
269269
background: $primary 10%;
270270
}
271271
272-
DataTable > .datatable--cursor {
272+
DataTable > .datatable--cursor {
273273
background: $secondary;
274274
color: $text;
275275
}

tests/test_app.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from textual.app import App
1+
from textual.app import App, ComposeResult
2+
from textual.widgets import Button
23

34

45
def test_batch_update():
@@ -15,3 +16,23 @@ def test_batch_update():
1516
assert app._batch_count == 1 # Exiting decrements
1617

1718
assert app._batch_count == 0 # Back to zero
19+
20+
21+
class MyApp(App):
22+
def compose(self) -> ComposeResult:
23+
yield Button("Click me!")
24+
25+
26+
async def test_hover_update_styles():
27+
app = MyApp()
28+
async with app.run_test() as pilot:
29+
button = app.query_one(Button)
30+
assert button.pseudo_classes == {"enabled"}
31+
32+
# Take note of the initial background colour
33+
initial_background = button.styles.background
34+
await pilot.hover(Button)
35+
36+
# We've hovered, so ensure the pseudoclass is present and background changed
37+
assert button.pseudo_classes == {"enabled", "hover"}
38+
assert button.styles.background != initial_background

tests/test_cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
def test_lru_cache():
99
cache = LRUCache(3)
1010

11-
assert str(cache) == "<LRUCache maxsize=3 hits=0 misses=0>"
11+
assert str(cache) == "<LRUCache size=0 maxsize=3 hits=0 misses=0>"
1212

1313
# insert some values
1414
cache["foo"] = 1
@@ -65,7 +65,7 @@ def test_lru_cache_hits():
6565
assert cache.hits == 3
6666
assert cache.misses == 2
6767

68-
assert str(cache) == "<LRUCache maxsize=4 hits=3 misses=2>"
68+
assert str(cache) == "<LRUCache size=1 maxsize=4 hits=3 misses=2>"
6969

7070

7171
def test_lru_cache_get():

0 commit comments

Comments
 (0)