mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Modes docs (#3233)
* Modes docs * Added current mode * fix docstring * diagrams * Update docs/guide/screens.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * Update docs/guide/screens.md Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com> * words --------- Co-authored-by: Rodrigo Girão Serrão <5621605+rodrigogiraoserrao@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
- TCSS styles `layer` and `layers` can be strings https://github.com/Textualize/textual/pull/3169
|
||||
- `App.return_code` for the app return code https://github.com/Textualize/textual/pull/3202
|
||||
- Added `animate` switch to `Tree.scroll_to_line` and `Tree.scroll_to_node` https://github.com/Textualize/textual/pull/3210
|
||||
- Added App.current_mode to get the current mode https://github.com/Textualize/textual/pull/3233
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
42
docs/examples/guide/screens/modes01.py
Normal file
42
docs/examples/guide/screens/modes01.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from textual.app import App, ComposeResult
|
||||
from textual.screen import Screen
|
||||
from textual.widgets import Footer, Placeholder
|
||||
|
||||
|
||||
class DashboardScreen(Screen):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Placeholder("Dashboard Screen")
|
||||
yield Footer()
|
||||
|
||||
|
||||
class SettingsScreen(Screen):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Placeholder("Settings Screen")
|
||||
yield Footer()
|
||||
|
||||
|
||||
class HelpScreen(Screen):
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Placeholder("Help Screen")
|
||||
yield Footer()
|
||||
|
||||
|
||||
class ModesApp(App):
|
||||
BINDINGS = [
|
||||
("d", "switch_mode('dashboard')", "Dashboard"), # (1)!
|
||||
("s", "switch_mode('settings')", "Settings"),
|
||||
("h", "switch_mode('help')", "Help"),
|
||||
]
|
||||
MODES = {
|
||||
"dashboard": DashboardScreen, # (2)!
|
||||
"settings": SettingsScreen,
|
||||
"help": HelpScreen,
|
||||
}
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.switch_mode("dashboard") # (3)!
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = ModesApp()
|
||||
app.run()
|
||||
@@ -256,3 +256,63 @@ Returning data in this way can help keep your code manageable by making it easy
|
||||
|
||||
You may have noticed in the previous example that we changed the base class to `ModalScreen[bool]`.
|
||||
The addition of `[bool]` adds typing information that tells the type checker to expect a boolean in the call to `dismiss`, and that any callback set in `push_screen` should also expect the same type. As always, typing is optional in Textual, but this may help you catch bugs.
|
||||
|
||||
|
||||
## Modes
|
||||
|
||||
Some apps may benefit from having multiple screen stacks, rather than just one.
|
||||
Consider an app with a dashboard screen, a settings screen, and a help screen.
|
||||
These are independent in the sense that we don't want to prevent the user from switching between them, even if there are one or more modal screens on the screen stack.
|
||||
But we may still want each individual screen to have a navigation stack where we can push and pop screens.
|
||||
|
||||
In Textual we can manage this with *modes*.
|
||||
A mode is simply a named screen stack, which we can switch between as required.
|
||||
When we switch modes, the topmost screen in the new mode becomes the active visible screen.
|
||||
|
||||
The following diagram illustrates such an app with modes.
|
||||
On startup the app switches to the "dashboard" mode which makes the top of the stack visible.
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/screens/modes1.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
If we later change the mode to "settings", the top of that mode's screen stack becomes visible.
|
||||
|
||||
<div class="excalidraw">
|
||||
--8<-- "docs/images/screens/modes2.excalidraw.svg"
|
||||
</div>
|
||||
|
||||
To add modes to your app, define a [`MODES`][textual.app.App.MODES] class variable in your App class which should be a `dict` that maps the name of the mode on to either a screen object, a callable that returns a screen, or the name of an installed screen.
|
||||
However you specify it, the values in `MODES` set the base screen for each mode's screen stack.
|
||||
|
||||
You can switch between these screens at any time by calling [`App.switch_mode`][textual.app.App.switch_mode].
|
||||
When you switch to a new mode, the topmost screen in the new stack becomes visible.
|
||||
Any calls to [`App.push_screen`][textual.app.App.push_screen] or [`App.pop_screen`][textual.app.App.pop_screen] will affect only the active mode.
|
||||
|
||||
Let's look at an example with modes:
|
||||
|
||||
=== "modes01.py"
|
||||
|
||||
```python hl_lines="25-29 30-34 37"
|
||||
--8<-- "docs/examples/guide/screens/modes01.py"
|
||||
```
|
||||
|
||||
1. `switch_mode` is a builtin action to switch modes.
|
||||
2. Associates `DashboardScreen` with the name "dashboard".
|
||||
3. Switches to the dashboard mode.
|
||||
|
||||
=== "Output"
|
||||
|
||||
```{.textual path="docs/examples/guide/screens/modes01.py"}
|
||||
```
|
||||
|
||||
=== "Output (after pressing S)"
|
||||
|
||||
```{.textual path="docs/examples/guide/screens/modes01.py", press="s"}
|
||||
```
|
||||
|
||||
Here we have defined three screens.
|
||||
One for a dashboard, one for settings, and one for help.
|
||||
We've bound keys to each of these screens, so the user can switch between the screens.
|
||||
|
||||
Pressing ++d++, ++s++, or ++h++ switches between these modes.
|
||||
|
||||
16
docs/images/screens/modes1.excalidraw.svg
Normal file
16
docs/images/screens/modes1.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
16
docs/images/screens/modes2.excalidraw.svg
Normal file
16
docs/images/screens/modes2.excalidraw.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
@@ -668,6 +668,11 @@ class App(Generic[ReturnType], DOMNode):
|
||||
"""
|
||||
return self._screen_stacks[self._current_mode]
|
||||
|
||||
@property
|
||||
def current_mode(self) -> str:
|
||||
"""The name of the currently active mode."""
|
||||
return self._current_mode
|
||||
|
||||
def exit(
|
||||
self,
|
||||
result: ReturnType | None = None,
|
||||
@@ -1535,19 +1540,19 @@ class App(Generic[ReturnType], DOMNode):
|
||||
return self.mount(*widgets, before=before, after=after)
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
mode: Name of the mode.
|
||||
"""
|
||||
|
||||
stack = self._screen_stacks.get(mode, [])
|
||||
if not stack:
|
||||
_screen = self.MODES[mode]
|
||||
if callable(_screen):
|
||||
screen, _ = self._get_screen(_screen())
|
||||
else:
|
||||
screen, _ = self._get_screen(self.MODES[mode])
|
||||
new_screen: Screen | str = _screen() if callable(_screen) else _screen
|
||||
screen, _ = self._get_screen(new_screen)
|
||||
stack.append(screen)
|
||||
|
||||
self._load_screen_css(screen)
|
||||
|
||||
self._screen_stacks[mode] = stack
|
||||
|
||||
def switch_mode(self, mode: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user