mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
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:
@@ -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.
|
- Changed signature of Driver. Technically a breaking change, but unlikely to affect anyone.
|
||||||
- Breaking change: Timer.start is now private, and returns No
|
- 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
|
- 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
|
- `ButtonVariant` is now exported from `textual.widgets.button` https://github.com/Textualize/textual/issues/2264
|
||||||
|
|
||||||
### Added
|
### 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
|
- 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 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
|
- Fixed `active_message_pump.get` sometimes resulting in a `LookupError` https://github.com/Textualize/textual/issues/2301
|
||||||
|
|
||||||
|
|
||||||
## [0.19.1] - 2023-04-10
|
## [0.19.1] - 2023-04-10
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -72,9 +72,7 @@ class LRUCache(Generic[CacheKey, CacheValue]):
|
|||||||
return len(self._cache)
|
return len(self._cache)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return (
|
return f"<LRUCache size={len(self)} maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
|
||||||
f"<LRUCache maxsize={self._maxsize} hits={self.hits} misses={self.misses}>"
|
|
||||||
)
|
|
||||||
|
|
||||||
def grow(self, maxsize: int) -> None:
|
def grow(self, maxsize: int) -> None:
|
||||||
"""Grow the maximum size to at least `maxsize` elements.
|
"""Grow the maximum size to at least `maxsize` elements.
|
||||||
|
|||||||
@@ -322,7 +322,6 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self.design = DEFAULT_COLORS
|
self.design = DEFAULT_COLORS
|
||||||
|
|
||||||
self.stylesheet = Stylesheet(variables=self.get_css_variables())
|
self.stylesheet = Stylesheet(variables=self.get_css_variables())
|
||||||
self._require_stylesheet_update: set[DOMNode] = set()
|
|
||||||
|
|
||||||
css_path = css_path or self.CSS_PATH
|
css_path = css_path or self.CSS_PATH
|
||||||
if css_path is not None:
|
if css_path is not None:
|
||||||
@@ -1229,13 +1228,15 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
return self.screen.get_child_by_type(expect_type)
|
return self.screen.get_child_by_type(expect_type)
|
||||||
|
|
||||||
def update_styles(self, node: DOMNode | None = None) -> None:
|
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.
|
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)
|
descendants = node.walk_children(with_self=True)
|
||||||
self.check_idle()
|
self.stylesheet.update_nodes(descendants, animate=True)
|
||||||
|
|
||||||
def mount(
|
def mount(
|
||||||
self,
|
self,
|
||||||
@@ -1773,14 +1774,6 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
|
|
||||||
def _on_idle(self) -> None:
|
def _on_idle(self) -> None:
|
||||||
"""Perform actions when there are no messages in the queue."""
|
"""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(
|
def _register_child(
|
||||||
self, parent: DOMNode, child: Widget, before: int | None, after: int | None
|
self, parent: DOMNode, child: Widget, before: int | None, after: int | None
|
||||||
|
|||||||
@@ -530,7 +530,6 @@ class Stylesheet:
|
|||||||
nodes: Nodes to update.
|
nodes: Nodes to update.
|
||||||
animate: Enable CSS animation.
|
animate: Enable CSS animation.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules_map = self.rules_map
|
rules_map = self.rules_map
|
||||||
apply = self.apply
|
apply = self.apply
|
||||||
|
|
||||||
|
|||||||
@@ -252,6 +252,7 @@ class ClientHandler:
|
|||||||
):
|
):
|
||||||
await self.incoming_queue.put(message)
|
await self.incoming_queue.put(message)
|
||||||
elif websocket_message.type == WSMsgType.ERROR:
|
elif websocket_message.type == WSMsgType.ERROR:
|
||||||
|
self.service.console.print(websocket_message.data)
|
||||||
self.service.console.print(
|
self.service.console.print(
|
||||||
DevConsoleNotice("Websocket error occurred", level="error")
|
DevConsoleNotice("Websocket error occurred", level="error")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class DataTable(ScrollView, Generic[CellType], can_focus=True):
|
|||||||
background: $primary 10%;
|
background: $primary 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataTable > .datatable--cursor {
|
DataTable > .datatable--cursor {
|
||||||
background: $secondary;
|
background: $secondary;
|
||||||
color: $text;
|
color: $text;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from textual.app import App
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Button
|
||||||
|
|
||||||
|
|
||||||
def test_batch_update():
|
def test_batch_update():
|
||||||
@@ -15,3 +16,23 @@ def test_batch_update():
|
|||||||
assert app._batch_count == 1 # Exiting decrements
|
assert app._batch_count == 1 # Exiting decrements
|
||||||
|
|
||||||
assert app._batch_count == 0 # Back to zero
|
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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from textual._cache import FIFOCache, LRUCache
|
|||||||
def test_lru_cache():
|
def test_lru_cache():
|
||||||
cache = LRUCache(3)
|
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
|
# insert some values
|
||||||
cache["foo"] = 1
|
cache["foo"] = 1
|
||||||
@@ -65,7 +65,7 @@ def test_lru_cache_hits():
|
|||||||
assert cache.hits == 3
|
assert cache.hits == 3
|
||||||
assert cache.misses == 2
|
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():
|
def test_lru_cache_get():
|
||||||
|
|||||||
Reference in New Issue
Block a user