Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

screen docs #3955

Merged
merged 13 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed

- Breaking change: `Widget.move_child` parameters `before` and `after` are now keyword-only https://github.com/Textualize/textual/pull/3896
- Style tweak to toasts https://github.com/Textualize/textual/pull/3955

### Added

- Added textual.lazy https://github.com/Textualize/textual/pull/3936
- Added App.push_screen_wait https://github.com/Textualize/textual/pull/3955

## [0.46.0] - 2023-12-17

Expand Down
45 changes: 45 additions & 0 deletions docs/examples/guide/screens/questions01.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from textual import on, work
from textual.app import App, ComposeResult
from textual.screen import Screen
from textual.widgets import Button, Label


class QuestionScreen(Screen[bool]):
"""Screen with a parameter."""

def __init__(self, question: str) -> None:
self.question = question
super().__init__()

def compose(self) -> ComposeResult:
yield Label(self.question)
yield Button("Yes", id="yes", variant="success")
yield Button("No", id="no")

@on(Button.Pressed, "#yes")
def handle_yes(self) -> None:
self.dismiss(True) # (1)!

@on(Button.Pressed, "#no")
def handle_no(self) -> None:
self.dismiss(False) # (2)!


class QuestionsApp(App):
"""Demonstrates wait_for_dismiss"""

CSS_PATH = "questions01.tcss"

@work # (3)!
async def on_mount(self) -> None:
if await self.push_screen_wait( # (4)!
QuestionScreen("Do you like Textual?"),
):
self.notify("Good answer!")
else:
self.notify(":-(", severity="error")


if __name__ == "__main__":
app = QuestionsApp()
app.run()
17 changes: 17 additions & 0 deletions docs/examples/guide/screens/questions01.tcss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
QuestionScreen {
layout: grid;
grid-size: 2 2;
align: center bottom;
}

QuestionScreen > Label {
margin: 1;
text-align: center;
column-span: 2;
width: 1fr;
}

QuestionScreen Button {
margin: 2;
width: 1fr;
}
39 changes: 39 additions & 0 deletions docs/guide/screens.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,45 @@ You may have noticed in the previous example that we changed the base class to `
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.


### Waiting for screens

It is also possible to wait on a screen to be dismissed, which can feel like a more natural way of expressing logic than a callback.
The [`push_screen_wait()`][textual.app.App.push_screen_wait] method will push a screen and wait for its result (the value from [`Screen.dismiss()`][textual.screen.Screen.dismiss]).

This can only be done from a [worker](./workers.md), so that waiting for the screen doesn't prevent your app from updating.

Let's look at an example that uses `push_screen_wait` to ask a question and waits for the user to reply by clicking a button.


=== "questions01.py"

```python title="questions01.py" hl_lines="35-37"
--8<-- "docs/examples/guide/screens/questions01.py"
```

1. Dismiss with `True` when pressing the Yes button.
2. Dismiss with `False` when pressing the No button.
3. The `work` decorator will make this method run in a worker (background task).
4. Will return a result when the user clicks one of the buttons.


=== "questions01.tcss"

```css title="questions01.tcss"
--8<-- "docs/examples/guide/screens/questions01.tcss"
```

=== "Output"

```{.textual path="docs/examples/guide/screens/questions01.py"}
```

The mount handler on the app is decorated with `@work`, which makes the code run in a worker (background task).
In the mount handler we push the screen with the `push_screen_wait`.
When the user presses one of the buttons, the screen calls [`dismiss()`][textual.screen.Screen.dismiss] with either `True` or `False`.
This value is then returned from the `push_screen_wait` method in the mount handler.


## Modes

Some apps may benefit from having multiple screen stacks, rather than just one.
Expand Down
23 changes: 23 additions & 0 deletions src/textual/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,29 @@ def push_screen(
else:
return await_mount

@overload
async def push_screen_wait(
self, screen: Screen[ScreenResultType]
) -> ScreenResultType:
...

@overload
async def push_screen_wait(self, screen: str) -> Any:
...

async def push_screen_wait(
self, screen: Screen[ScreenResultType] | str
) -> ScreenResultType | Any:
"""Push a screen and wait for the result (received from [`Screen.dismiss`][textual.screen.Screen.dismiss]).

Args:
screen: A screen or the name of an installed screen.

Returns:
The screen's result.
"""
return await self.push_screen(screen, wait_for_dismiss=True)

def switch_screen(self, screen: Screen | str) -> AwaitMount:
"""Switch to another [screen](/guide/screens) by replacing the top of the screen stack with a new screen.

Expand Down
8 changes: 4 additions & 4 deletions src/textual/widgets/_toast.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,27 @@ class Toast(Static, inherit_css=False):
}

Toast {
border-right: wide $background;
border-right: outer $background;
}

Toast.-information {
border-left: wide $success;
border-left: outer $success;
}

Toast.-information .toast--title {
color: $success-darken-1;
}

Toast.-warning {
border-left: wide $warning;
border-left: outer $warning;
}

Toast.-warning .toast--title {
color: $warning-darken-1;
}

Toast.-error {
border-left: wide $error;
border-left: outer $error;
}

Toast.-error .toast--title {
Expand Down
692 changes: 346 additions & 346 deletions tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Large diffs are not rendered by default.

Loading