Skip to content

Commit e5f2231

Browse files
screen docs (#3955)
* screen docs * Added push_screen_wait * words and test * overload typing * docstring * style tweak * ws * Update docs/guide/screens.md Co-authored-by: Rodrigo Girão Serrão <[email protected]> * Update docs/guide/screens.md Co-authored-by: Rodrigo Girão Serrão <[email protected]> * Update src/textual/app.py Co-authored-by: Rodrigo Girão Serrão <[email protected]> * merge fix * wording * wording [skipci] --------- Co-authored-by: Rodrigo Girão Serrão <[email protected]>
1 parent babbc40 commit e5f2231

File tree

7 files changed

+476
-350
lines changed

7 files changed

+476
-350
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1616
### Changed
1717

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

2021
### Added
2122

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

2426
## [0.46.0] - 2023-12-17
2527

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from textual import on, work
2+
from textual.app import App, ComposeResult
3+
from textual.screen import Screen
4+
from textual.widgets import Button, Label
5+
6+
7+
class QuestionScreen(Screen[bool]):
8+
"""Screen with a parameter."""
9+
10+
def __init__(self, question: str) -> None:
11+
self.question = question
12+
super().__init__()
13+
14+
def compose(self) -> ComposeResult:
15+
yield Label(self.question)
16+
yield Button("Yes", id="yes", variant="success")
17+
yield Button("No", id="no")
18+
19+
@on(Button.Pressed, "#yes")
20+
def handle_yes(self) -> None:
21+
self.dismiss(True) # (1)!
22+
23+
@on(Button.Pressed, "#no")
24+
def handle_no(self) -> None:
25+
self.dismiss(False) # (2)!
26+
27+
28+
class QuestionsApp(App):
29+
"""Demonstrates wait_for_dismiss"""
30+
31+
CSS_PATH = "questions01.tcss"
32+
33+
@work # (3)!
34+
async def on_mount(self) -> None:
35+
if await self.push_screen_wait( # (4)!
36+
QuestionScreen("Do you like Textual?"),
37+
):
38+
self.notify("Good answer!")
39+
else:
40+
self.notify(":-(", severity="error")
41+
42+
43+
if __name__ == "__main__":
44+
app = QuestionsApp()
45+
app.run()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
QuestionScreen {
2+
layout: grid;
3+
grid-size: 2 2;
4+
align: center bottom;
5+
}
6+
7+
QuestionScreen > Label {
8+
margin: 1;
9+
text-align: center;
10+
column-span: 2;
11+
width: 1fr;
12+
}
13+
14+
QuestionScreen Button {
15+
margin: 2;
16+
width: 1fr;
17+
}

docs/guide/screens.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,45 @@ You may have noticed in the previous example that we changed the base class to `
258258
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.
259259

260260

261+
### Waiting for screens
262+
263+
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.
264+
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]).
265+
266+
This can only be done from a [worker](./workers.md), so that waiting for the screen doesn't prevent your app from updating.
267+
268+
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.
269+
270+
271+
=== "questions01.py"
272+
273+
```python title="questions01.py" hl_lines="35-37"
274+
--8<-- "docs/examples/guide/screens/questions01.py"
275+
```
276+
277+
1. Dismiss with `True` when pressing the Yes button.
278+
2. Dismiss with `False` when pressing the No button.
279+
3. The `work` decorator will make this method run in a worker (background task).
280+
4. Will return a result when the user clicks one of the buttons.
281+
282+
283+
=== "questions01.tcss"
284+
285+
```css title="questions01.tcss"
286+
--8<-- "docs/examples/guide/screens/questions01.tcss"
287+
```
288+
289+
=== "Output"
290+
291+
```{.textual path="docs/examples/guide/screens/questions01.py"}
292+
```
293+
294+
The mount handler on the app is decorated with `@work`, which makes the code run in a worker (background task).
295+
In the mount handler we push the screen with the `push_screen_wait`.
296+
When the user presses one of the buttons, the screen calls [`dismiss()`][textual.screen.Screen.dismiss] with either `True` or `False`.
297+
This value is then returned from the `push_screen_wait` method in the mount handler.
298+
299+
261300
## Modes
262301

263302
Some apps may benefit from having multiple screen stacks, rather than just one.

src/textual/app.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,6 +1945,29 @@ def push_screen(
19451945
else:
19461946
return await_mount
19471947

1948+
@overload
1949+
async def push_screen_wait(
1950+
self, screen: Screen[ScreenResultType]
1951+
) -> ScreenResultType:
1952+
...
1953+
1954+
@overload
1955+
async def push_screen_wait(self, screen: str) -> Any:
1956+
...
1957+
1958+
async def push_screen_wait(
1959+
self, screen: Screen[ScreenResultType] | str
1960+
) -> ScreenResultType | Any:
1961+
"""Push a screen and wait for the result (received from [`Screen.dismiss`][textual.screen.Screen.dismiss]).
1962+
1963+
Args:
1964+
screen: A screen or the name of an installed screen.
1965+
1966+
Returns:
1967+
The screen's result.
1968+
"""
1969+
return await self.push_screen(screen, wait_for_dismiss=True)
1970+
19481971
def switch_screen(self, screen: Screen | str) -> AwaitMount:
19491972
"""Switch to another [screen](/guide/screens) by replacing the top of the screen stack with a new screen.
19501973

src/textual/widgets/_toast.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,27 @@ class Toast(Static, inherit_css=False):
5858
}
5959
6060
Toast {
61-
border-right: wide $background;
61+
border-right: outer $background;
6262
}
6363
6464
Toast.-information {
65-
border-left: wide $success;
65+
border-left: outer $success;
6666
}
6767
6868
Toast.-information .toast--title {
6969
color: $success-darken-1;
7070
}
7171
7272
Toast.-warning {
73-
border-left: wide $warning;
73+
border-left: outer $warning;
7474
}
7575
7676
Toast.-warning .toast--title {
7777
color: $warning-darken-1;
7878
}
7979
8080
Toast.-error {
81-
border-left: wide $error;
81+
border-left: outer $error;
8282
}
8383
8484
Toast.-error .toast--title {

tests/snapshot_tests/__snapshots__/test_snapshots.ambr

Lines changed: 346 additions & 346 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)