mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Refactor screen stack modes.
This commit is contained in:
@@ -163,6 +163,10 @@ class ModeError(Exception):
|
|||||||
"""Base class for exceptions related to modes."""
|
"""Base class for exceptions related to modes."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidModeError(ModeError):
|
||||||
|
"""Raised if there is an issue with a mode name."""
|
||||||
|
|
||||||
|
|
||||||
class UnknownModeError(ModeError):
|
class UnknownModeError(ModeError):
|
||||||
"""Raised when attempting to use a mode that is not known."""
|
"""Raised when attempting to use a mode that is not known."""
|
||||||
|
|
||||||
@@ -224,8 +228,35 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MODES: ClassVar[set[str]] = set()
|
MODES: ClassVar[dict[str, str | Screen | Callable[[], Screen]]] = {}
|
||||||
"""Modes associated with the app for the lifetime of the app."""
|
"""Modes associated with the app and their base screens.
|
||||||
|
|
||||||
|
The base screen is the screen at the bottom of the mode stack. You can think of
|
||||||
|
it as the default screen for that stack.
|
||||||
|
The base screens can be names of screens listed in [SCREENS][textual.app.App.SCREENS],
|
||||||
|
[`Screen`][textual.screen.Screen] instances, or callables that return screens.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```py
|
||||||
|
class HelpScreen(Screen[None]):
|
||||||
|
...
|
||||||
|
|
||||||
|
class MainAppScreen(Screen[None]):
|
||||||
|
...
|
||||||
|
|
||||||
|
class MyApp(App[None]):
|
||||||
|
MODES = {
|
||||||
|
"default": "main",
|
||||||
|
"help": HelpScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
SCREENS = {
|
||||||
|
"main": MainAppScreen,
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
"""
|
||||||
SCREENS: ClassVar[dict[str, Screen | Callable[[], Screen]]] = {}
|
SCREENS: ClassVar[dict[str, Screen | Callable[[], Screen]]] = {}
|
||||||
"""Screens associated with the app for the lifetime of the app."""
|
"""Screens associated with the app for the lifetime of the app."""
|
||||||
_BASE_PATH: str | None = None
|
_BASE_PATH: str | None = None
|
||||||
@@ -1348,8 +1379,14 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
def _init_mode(self, mode: str) -> None:
|
def _init_mode(self, mode: str) -> None:
|
||||||
"""Do internal initialisation of a new screen stack mode."""
|
"""Do internal initialisation of a new screen stack mode."""
|
||||||
|
|
||||||
screen = Screen(id=f"_default_{mode}")
|
stack = self._screen_stacks.get(mode, [])
|
||||||
self._register(self, screen)
|
if not stack:
|
||||||
|
_screen = self.MODES[mode]
|
||||||
|
if callable(_screen):
|
||||||
|
screen, _ = self._get_screen(_screen())
|
||||||
|
else:
|
||||||
|
screen, _ = self._get_screen(self.MODES[mode])
|
||||||
|
stack.append(screen)
|
||||||
self._screen_stacks[mode] = [screen]
|
self._screen_stacks[mode] = [screen]
|
||||||
|
|
||||||
def switch_mode(self, mode: str) -> None:
|
def switch_mode(self, mode: str) -> None:
|
||||||
@@ -1372,19 +1409,33 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self._current_mode = mode
|
self._current_mode = mode
|
||||||
self.screen._screen_resized(self.size)
|
self.screen._screen_resized(self.size)
|
||||||
self.screen.post_message(events.ScreenResume())
|
self.screen.post_message(events.ScreenResume())
|
||||||
|
self.log.system(f"{self._current_mode!r} is the current mode")
|
||||||
self.log.system(f"{self.screen} is active")
|
self.log.system(f"{self.screen} is active")
|
||||||
|
|
||||||
def add_mode(self, mode: str) -> None:
|
def add_mode(
|
||||||
"""Adds a mode to the app.
|
self, mode: str, base_screen: str | Screen | Callable[[], Screen]
|
||||||
|
) -> None:
|
||||||
|
"""Adds a mode and its corresponding base screen to the app.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mode: The new mode.
|
mode: The new mode.
|
||||||
|
base_screen: The base screen associated with the given mode.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
InvalidModeError: If the name of the mode is not valid/duplicated.
|
||||||
"""
|
"""
|
||||||
self.MODES.add(mode)
|
if mode == "_default":
|
||||||
|
raise InvalidModeError("Cannot use '_default' as a custom mode.")
|
||||||
|
elif mode in self.MODES:
|
||||||
|
raise InvalidModeError(f"Duplicated mode name {mode!r}.")
|
||||||
|
|
||||||
|
self.MODES[mode] = base_screen
|
||||||
|
|
||||||
def remove_mode(self, mode: str) -> None:
|
def remove_mode(self, mode: str) -> None:
|
||||||
"""Removes a mode from the app.
|
"""Removes a mode from the app.
|
||||||
|
|
||||||
|
Screens that are running in the stack of that mode are scheduled for pruning.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mode: The mode to remove. It can't be the active mode.
|
mode: The mode to remove. It can't be the active mode.
|
||||||
|
|
||||||
@@ -1396,11 +1447,17 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
raise ActiveModeError(f"Can't remove active mode {mode!r}")
|
raise ActiveModeError(f"Can't remove active mode {mode!r}")
|
||||||
elif mode not in self.MODES:
|
elif mode not in self.MODES:
|
||||||
raise UnknownModeError(f"Unknown mode {mode!r}")
|
raise UnknownModeError(f"Unknown mode {mode!r}")
|
||||||
|
else:
|
||||||
|
del self.MODES[mode]
|
||||||
|
|
||||||
stack = self._screen_stacks.get(mode, [])
|
if mode not in self._screen_stacks:
|
||||||
while stack:
|
return
|
||||||
self._replace_screen(stack.pop())
|
|
||||||
self.MODES.remove(mode)
|
stack = self._screen_stacks[mode]
|
||||||
|
for screen in reversed(stack):
|
||||||
|
if screen._running:
|
||||||
|
self.call_later(self._prune_node, screen)
|
||||||
|
del self._screen_stacks[mode]
|
||||||
|
|
||||||
def is_screen_installed(self, screen: Screen | str) -> bool:
|
def is_screen_installed(self, screen: Screen | str) -> bool:
|
||||||
"""Check if a given screen has been installed.
|
"""Check if a given screen has been installed.
|
||||||
|
|||||||
Reference in New Issue
Block a user