call later

This commit is contained in:
Will McGugan
2022-11-09 17:23:28 +00:00
parent 51d5e7db0c
commit 39a764f49f
9 changed files with 88 additions and 22 deletions

View File

@@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed
- Watchers are now called immediately when setting the attribute if they are synchronous. https://github.com/Textualize/textual/pull/1145
- Widget.call_later has been renamed to Widget.call_after_refresh.
### Added
- Added Widget.call_later which invokes a callback on idle.
## [0.4.0] - 2022-11-08

View File

@@ -23,7 +23,7 @@ class BindingApp(App):
bar = Bar(color)
bar.styles.background = Color.parse(color).with_alpha(0.5)
self.mount(bar)
self.call_later(self.screen.scroll_end, animate=False)
self.call_after_refresh(self.screen.scroll_end, animate=False)
if __name__ == "__main__":

View File

@@ -1606,19 +1606,28 @@ class App(Generic[ReturnType], DOMNode):
screen (Screen): Screen instance
renderable (RenderableType): A Rich renderable.
"""
if screen is not self.screen or renderable is None:
return
if self._running and not self._closed and not self.is_headless:
console = self.console
self._begin_update()
try:
try:
if screen is not self.screen or renderable is None:
return
if self._running and not self._closed and not self.is_headless:
console = self.console
self._begin_update()
try:
console.print(renderable)
except Exception as error:
self._handle_exception(error)
finally:
self._end_update()
console.file.flush()
try:
print(renderable)
console.print(renderable)
except Exception as error:
self._handle_exception(error)
finally:
self._end_update()
console.file.flush()
finally:
self.post_display_hook()
def post_display_hook(self) -> None:
"""Called immediately after a display is done. Used in tests."""
def get_widget_at(self, x: int, y: int) -> tuple[Widget, Region]:
"""Get the widget under the given coordinates.

View File

@@ -71,7 +71,7 @@ class ColorsApp(App):
yield Footer()
def on_mount(self) -> None:
self.call_later(self.update_view)
self.call_after_refresh(self.update_view)
def update_view(self) -> None:
content = self.query_one("Content", Content)

View File

@@ -251,7 +251,7 @@ class MessagePump(metaclass=MessagePumpMeta):
self._timers.add(timer)
return timer
def call_later(self, callback: Callable, *args, **kwargs) -> None:
def call_after_refresh(self, callback: Callable, *args, **kwargs) -> None:
"""Schedule a callback to run after all messages are processed and the screen
has been refreshed. Positional and keyword arguments are passed to the callable.
@@ -263,6 +263,16 @@ class MessagePump(metaclass=MessagePumpMeta):
message = messages.InvokeLater(self, partial(callback, *args, **kwargs))
self.post_message_no_wait(message)
def call_later(self, callback: Callable, *args, **kwargs) -> None:
"""Schedule a callback to run after all messages are processed in this object.
Positional and keywords arguments are passed to the callable.
Args:
callback (Callable): Callable to call next.
"""
message = events.Callback(self, callback=partial(callback, *args, **kwargs))
self.post_message_no_wait(message)
def _on_invoke_later(self, message: messages.InvokeLater) -> None:
# Forward InvokeLater message to the Screen
self.app.screen._invoke_later(message.callback)

View File

@@ -333,11 +333,11 @@ class Screen(Widget):
self._compositor.update_widgets(self._dirty_widgets)
self.app._display(self, self._compositor.render())
self._dirty_widgets.clear()
self.update_timer.pause()
if self._callbacks:
self.post_message_no_wait(events.InvokeCallbacks(self))
self.update_timer.pause()
async def _on_invoke_callbacks(self, event: events.InvokeCallbacks) -> None:
"""Handle PostScreenUpdate events, which are sent after the screen is updated"""
await self._invoke_and_clear_callbacks()
@@ -346,6 +346,8 @@ class Screen(Widget):
"""If there are scheduled callbacks to run, call them and clear
the callback queue."""
if self._callbacks:
display_update = self._compositor.render(full=True)
self.app._display(self, display_update)
callbacks = self._callbacks[:]
self._callbacks.clear()
for callback in callbacks:
@@ -402,8 +404,7 @@ class Screen(Widget):
self.app._handle_exception(error)
return
display_update = self._compositor.render(full=full)
if display_update is not None:
self.app._display(self, display_update)
self.app._display(self, display_update)
async def _on_update(self, message: messages.Update) -> None:
message.stop()

View File

@@ -1576,7 +1576,7 @@ class Widget(DOMNode):
"""
parent = self.parent
if isinstance(parent, Widget):
self.call_later(
self.call_after_refresh(
parent.scroll_to_widget,
self,
animate=animate,
@@ -1989,7 +1989,7 @@ class Widget(DOMNode):
except NoScreen:
pass
self.app.call_later(set_focus, self)
self.app.call_after_refresh(set_focus, self)
def reset_focus(self) -> None:
"""Reset the focus (move it to the next available widget)."""

View File

@@ -92,7 +92,7 @@ class DirectoryTree(TreeControl[DirEntry]):
self.render_tree_label.cache_clear()
def on_mount(self) -> None:
self.call_later(self.load_directory, self.root)
self.call_after_refresh(self.load_directory, self.root)
async def load_directory(self, node: TreeNode[DirEntry]):
path = node.data.path

41
tests/test_call_later.py Normal file
View File

@@ -0,0 +1,41 @@
import asyncio
from textual.app import App
class CallLaterApp(App[None]):
def __init__(self) -> None:
self.display_count = 0
super().__init__()
def post_display_hook(self) -> None:
self.display_count += 1
async def test_call_later() -> None:
"""Check that call later makes a call."""
app = CallLaterApp()
called_event = asyncio.Event()
async with app.run_test():
app.call_later(called_event.set)
await asyncio.wait_for(called_event.wait(), 1)
async def test_call_after_refresh() -> None:
"""Check that call later makes a call after a refresh."""
app = CallLaterApp()
display_count = -1
called_event = asyncio.Event()
def callback() -> None:
nonlocal display_count
called_event.set()
display_count = app.display_count
async with app.run_test():
app.call_after_refresh(callback)
await asyncio.wait_for(called_event.wait(), 1)
app_display_count = app.display_count
assert app_display_count > display_count