mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Initial testing of screen result callbacks
This is roughly how it should work. Having got this going and constructed test code to go with it (outwith of this commit, not unit testing code, just a test app to try out the ideas), I wanted to get this onto the forge for further mulling over tomorrow. The one sneaky/questionable thing here is that I'm sort of dumpster-diving the screen stack to get the "parent" screen, to make the callback in context. This both feels right and feels like a cheat. On the other hand it's public for a reason, right? Right?
This commit is contained in:
@@ -92,7 +92,7 @@ from .keys import (
|
||||
from .messages import CallbackType
|
||||
from .reactive import Reactive
|
||||
from .renderables.blank import Blank
|
||||
from .screen import Screen
|
||||
from .screen import Screen, ScreenResultCallbackType
|
||||
from .widget import AwaitMount, Widget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -1379,7 +1379,9 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self.log.system(f"{screen} REMOVED")
|
||||
return screen
|
||||
|
||||
def push_screen(self, screen: Screen | str) -> AwaitMount:
|
||||
def push_screen(
|
||||
self, screen: Screen | str, callback: ScreenResultCallbackType | None = None
|
||||
) -> AwaitMount:
|
||||
"""Push a new [screen](/guide/screens) on the screen stack, making it the current screen.
|
||||
|
||||
Args:
|
||||
@@ -1395,6 +1397,7 @@ class App(Generic[ReturnType], DOMNode):
|
||||
self.screen.post_message(events.ScreenSuspend())
|
||||
self.screen.refresh()
|
||||
next_screen, await_mount = self._get_screen(screen)
|
||||
next_screen._result_callback = callback
|
||||
self._screen_stack.append(next_screen)
|
||||
next_screen.post_message(events.ScreenResume())
|
||||
self.log.system(f"{self.screen} is current (PUSHED)")
|
||||
|
||||
@@ -6,7 +6,17 @@ The `Screen` class is a special widget which represents the content in the termi
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Iterable, Iterator
|
||||
from inspect import isawaitable
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Awaitable,
|
||||
Callable,
|
||||
Generic,
|
||||
Iterable,
|
||||
Iterator,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
import rich.repr
|
||||
from rich.console import RenderableType
|
||||
@@ -34,9 +44,17 @@ if TYPE_CHECKING:
|
||||
# Screen updates will be batched so that they don't happen more often than 120 times per second:
|
||||
UPDATE_PERIOD: Final[float] = 1 / 120
|
||||
|
||||
ScreenResultType = TypeVar("ScreenResultType")
|
||||
"""The result type of a screen."""
|
||||
|
||||
ScreenResultCallbackType = Union[
|
||||
Callable[[ScreenResultType], None], Callable[[ScreenResultType], Awaitable[None]]
|
||||
]
|
||||
"""Type of a screen result callback function."""
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class Screen(Widget):
|
||||
class Screen(Generic[ScreenResultType], Widget):
|
||||
"""The base class for screens."""
|
||||
|
||||
DEFAULT_CSS = """
|
||||
@@ -77,6 +95,7 @@ class Screen(Widget):
|
||||
self._dirty_widgets: set[Widget] = set()
|
||||
self.__update_timer: Timer | None = None
|
||||
self._callbacks: list[CallbackType] = []
|
||||
self._result_callback: ScreenResultCallbackType | None = None
|
||||
|
||||
@property
|
||||
def is_modal(self) -> bool:
|
||||
@@ -643,9 +662,33 @@ class Screen(Widget):
|
||||
else:
|
||||
self.post_message(event)
|
||||
|
||||
def dismiss(self) -> None:
|
||||
"""Dismiss the screen."""
|
||||
self.app.pop_screen()
|
||||
|
||||
def action_dismiss(self) -> None:
|
||||
self.dismiss()
|
||||
|
||||
def dismiss_with(self, result: ScreenResultType) -> None:
|
||||
"""Dismiss the screen with the given result.
|
||||
|
||||
Args:
|
||||
result: The result to be passed to the result callback.
|
||||
"""
|
||||
if self._result_callback is not None:
|
||||
try:
|
||||
callback_screen = self.app.screen_stack[-2]
|
||||
except IndexError:
|
||||
callback_screen = self
|
||||
callback_screen.call_next(self._result_callback, result)
|
||||
self.dismiss()
|
||||
|
||||
def action_dismiss_with(self, result: ScreenResultType) -> None:
|
||||
self.dismiss_with(result)
|
||||
|
||||
|
||||
@rich.repr.auto
|
||||
class ModalScreen(Screen):
|
||||
class ModalScreen(Screen[ScreenResultType]):
|
||||
"""A screen with bindings that take precedence over the App's key bindings.
|
||||
|
||||
The default styling of a modal screen will dim the screen underneath.
|
||||
|
||||
Reference in New Issue
Block a user