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
This commit is contained in:
darrenburns
2023-04-18 11:36:00 +01:00
committed by GitHub
parent 01d67173e8
commit 496f8b4524
8 changed files with 35 additions and 22 deletions

View File

@@ -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

View File

@@ -72,9 +72,7 @@ class LRUCache(Generic[CacheKey, CacheValue]):
return len(self._cache)
def __repr__(self) -> str:
return (
f"<LRUCache maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
)
return f"<LRUCache size={len(self)} maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
def grow(self, maxsize: int) -> None:
"""Grow the maximum size to at least `maxsize` elements.

View File

@@ -322,7 +322,6 @@ class App(Generic[ReturnType], DOMNode):
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 @@ class App(Generic[ReturnType], DOMNode):
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 @@ class App(Generic[ReturnType], DOMNode):
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

View File

@@ -530,7 +530,6 @@ class Stylesheet:
nodes: Nodes to update.
animate: Enable CSS animation.
"""
rules_map = self.rules_map
apply = self.apply

View File

@@ -252,6 +252,7 @@ class ClientHandler:
):
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")
)

View File

@@ -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;
}

View File

@@ -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

View File

@@ -8,7 +8,7 @@ from textual._cache import FIFOCache, LRUCache
def test_lru_cache():
cache = LRUCache(3)
assert str(cache) == "<LRUCache maxsize=3 hits=0 misses=0>"
assert str(cache) == "<LRUCache size=0 maxsize=3 hits=0 misses=0>"
# 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) == "<LRUCache maxsize=4 hits=3 misses=2>"
assert str(cache) == "<LRUCache size=1 maxsize=4 hits=3 misses=2>"
def test_lru_cache_get():