diff --git a/CHANGELOG.md b/CHANGELOG.md
index c872b70c9b..a241fdf406 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/docs/examples/guide/screens/questions01.py b/docs/examples/guide/screens/questions01.py
new file mode 100644
index 0000000000..cc699200c6
--- /dev/null
+++ b/docs/examples/guide/screens/questions01.py
@@ -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()
diff --git a/docs/examples/guide/screens/questions01.tcss b/docs/examples/guide/screens/questions01.tcss
new file mode 100644
index 0000000000..e56b2a949c
--- /dev/null
+++ b/docs/examples/guide/screens/questions01.tcss
@@ -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;
+}
diff --git a/docs/guide/screens.md b/docs/guide/screens.md
index e00640971e..4f57412fd0 100644
--- a/docs/guide/screens.md
+++ b/docs/guide/screens.md
@@ -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.
diff --git a/src/textual/app.py b/src/textual/app.py
index 8fce6798de..c900f7b2ba 100644
--- a/src/textual/app.py
+++ b/src/textual/app.py
@@ -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.
diff --git a/src/textual/widgets/_toast.py b/src/textual/widgets/_toast.py
index c212606eaf..ab920ebe63 100644
--- a/src/textual/widgets/_toast.py
+++ b/src/textual/widgets/_toast.py
@@ -58,11 +58,11 @@ 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 {
@@ -70,7 +70,7 @@ class Toast(Static, inherit_css=False):
}
Toast.-warning {
- border-left: wide $warning;
+ border-left: outer $warning;
}
Toast.-warning .toast--title {
@@ -78,7 +78,7 @@ class Toast(Static, inherit_css=False):
}
Toast.-error {
- border-left: wide $error;
+ border-left: outer $error;
}
Toast.-error .toast--title {
diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
index a6be20b3bb..c322a2b69f 100644
--- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
+++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr
@@ -23316,135 +23316,135 @@
font-weight: 700;
}
- .terminal-2986389628-matrix {
+ .terminal-1795141768-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-2986389628-title {
+ .terminal-1795141768-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-2986389628-r1 { fill: #e1e1e1 }
- .terminal-2986389628-r2 { fill: #c5c8c6 }
- .terminal-2986389628-r3 { fill: #56c278 }
- .terminal-2986389628-r4 { fill: #2e3339 }
- .terminal-2986389628-r5 { fill: #e3e4e4 }
- .terminal-2986389628-r6 { fill: #e3e4e4;text-decoration: underline; }
+ .terminal-1795141768-r1 { fill: #e1e1e1 }
+ .terminal-1795141768-r2 { fill: #c5c8c6 }
+ .terminal-1795141768-r3 { fill: #56c278 }
+ .terminal-1795141768-r4 { fill: #1d1d1d }
+ .terminal-1795141768-r5 { fill: #e3e4e4 }
+ .terminal-1795141768-r6 { fill: #e3e4e4;text-decoration: underline; }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- NotifyWithInlineLinkApp
+ NotifyWithInlineLinkApp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ▎▊
- ▎Click here for the bell sound.▊
- ▎▊
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ▌▐
+ ▌Click here for the bell sound.▐
+ ▌▐
@@ -23475,135 +23475,135 @@
font-weight: 700;
}
- .terminal-651867408-matrix {
+ .terminal-3756307740-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-651867408-title {
+ .terminal-3756307740-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-651867408-r1 { fill: #e1e1e1 }
- .terminal-651867408-r2 { fill: #c5c8c6 }
- .terminal-651867408-r3 { fill: #56c278 }
- .terminal-651867408-r4 { fill: #2e3339 }
- .terminal-651867408-r5 { fill: #e3e4e4 }
- .terminal-651867408-r6 { fill: #ddedf9;font-weight: bold }
+ .terminal-3756307740-r1 { fill: #e1e1e1 }
+ .terminal-3756307740-r2 { fill: #c5c8c6 }
+ .terminal-3756307740-r3 { fill: #56c278 }
+ .terminal-3756307740-r4 { fill: #1d1d1d }
+ .terminal-3756307740-r5 { fill: #e3e4e4 }
+ .terminal-3756307740-r6 { fill: #ddedf9;font-weight: bold }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- NotifyWithInlineLinkApp
+ NotifyWithInlineLinkApp
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ▎▊
- ▎Click here for the bell sound.▊
- ▎▊
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ▌▐
+ ▌Click here for the bell sound.▐
+ ▌▐
@@ -23634,139 +23634,139 @@
font-weight: 700;
}
- .terminal-3970684023-matrix {
+ .terminal-99275963-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-3970684023-title {
+ .terminal-99275963-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-3970684023-r1 { fill: #e1e1e1 }
- .terminal-3970684023-r2 { fill: #c5c8c6 }
- .terminal-3970684023-r3 { fill: #56c278 }
- .terminal-3970684023-r4 { fill: #2e3339 }
- .terminal-3970684023-r5 { fill: #e3e4e4 }
- .terminal-3970684023-r6 { fill: #feaa35 }
- .terminal-3970684023-r7 { fill: #e89719;font-weight: bold }
- .terminal-3970684023-r8 { fill: #e3e4e4;font-weight: bold }
- .terminal-3970684023-r9 { fill: #e3e4e4;font-weight: bold;font-style: italic; }
- .terminal-3970684023-r10 { fill: #bc4563 }
+ .terminal-99275963-r1 { fill: #e1e1e1 }
+ .terminal-99275963-r2 { fill: #c5c8c6 }
+ .terminal-99275963-r3 { fill: #56c278 }
+ .terminal-99275963-r4 { fill: #1d1d1d }
+ .terminal-99275963-r5 { fill: #e3e4e4 }
+ .terminal-99275963-r6 { fill: #feaa35 }
+ .terminal-99275963-r7 { fill: #e89719;font-weight: bold }
+ .terminal-99275963-r8 { fill: #e3e4e4;font-weight: bold }
+ .terminal-99275963-r9 { fill: #e3e4e4;font-weight: bold;font-style: italic; }
+ .terminal-99275963-r10 { fill: #bc4563 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- ToastApp
+ ToastApp
-
-
-
-
-
-
- ▎▊
- ▎It's an older code, sir, but it ▊
- ▎checks out.▊
- ▎▊
-
- ▎▊
- ▎Possible trap detected▊
- ▎Now witness the firepower of this▊
- ▎fully ARMED and OPERATIONAL▊
- ▎battle station!▊
- ▎▊
-
- ▎▊
- ▎It's a trap!▊
- ▎▊
-
- ▎▊
- ▎It's against my programming to ▊
- ▎impersonate a deity.▊
- ▎▊
+
+
+
+
+
+
+ ▌▐
+ ▌It's an older code, sir, but it ▐
+ ▌checks out.▐
+ ▌▐
+
+ ▌▐
+ ▌Possible trap detected▐
+ ▌Now witness the firepower of this▐
+ ▌fully ARMED and OPERATIONAL▐
+ ▌battle station!▐
+ ▌▐
+
+ ▌▐
+ ▌It's a trap!▐
+ ▌▐
+
+ ▌▐
+ ▌It's against my programming to ▐
+ ▌impersonate a deity.▐
+ ▌▐
@@ -23797,117 +23797,117 @@
font-weight: 700;
}
- .terminal-3060564477-matrix {
+ .terminal-61440561-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-3060564477-title {
+ .terminal-61440561-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-3060564477-r1 { fill: #c5c8c6 }
- .terminal-3060564477-r2 { fill: #56c278 }
- .terminal-3060564477-r3 { fill: #2e3339 }
- .terminal-3060564477-r4 { fill: #e3e4e4 }
+ .terminal-61440561-r1 { fill: #c5c8c6 }
+ .terminal-61440561-r2 { fill: #56c278 }
+ .terminal-61440561-r3 { fill: #1d1d1d }
+ .terminal-61440561-r4 { fill: #e3e4e4 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- LoadingOverlayApp
+ LoadingOverlayApp
-
-
-
-
-
-
-
-
-
- ▎▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎This is a big notification.▊
- ▎▊
- ▎▊
+
+
+
+
+
+
+
+
+
+ ▌▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌This is a big notification.▐
+ ▌▐
+ ▌▐
@@ -23938,134 +23938,134 @@
font-weight: 700;
}
- .terminal-2782348326-matrix {
+ .terminal-2569815150-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-2782348326-title {
+ .terminal-2569815150-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-2782348326-r1 { fill: #e1e1e1 }
- .terminal-2782348326-r2 { fill: #56c278 }
- .terminal-2782348326-r3 { fill: #c5c8c6 }
- .terminal-2782348326-r4 { fill: #2e3339 }
- .terminal-2782348326-r5 { fill: #e3e4e4 }
+ .terminal-2569815150-r1 { fill: #e1e1e1 }
+ .terminal-2569815150-r2 { fill: #56c278 }
+ .terminal-2569815150-r3 { fill: #c5c8c6 }
+ .terminal-2569815150-r4 { fill: #1d1d1d }
+ .terminal-2569815150-r5 { fill: #e3e4e4 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- NotifyThroughModesApp
+ NotifyThroughModesApp
-
-
-
- This is a mode screen▎▊
- ▎4▊
- ▎▊
-
- ▎▊
- ▎5▊
- ▎▊
-
- ▎▊
- ▎6▊
- ▎▊
-
- ▎▊
- ▎7▊
- ▎▊
-
- ▎▊
- ▎8▊
- ▎▊
-
- ▎▊
- ▎9▊
- ▎▊
+
+
+
+ This is a mode screen▌▐
+ ▌4▐
+ ▌▐
+
+ ▌▐
+ ▌5▐
+ ▌▐
+
+ ▌▐
+ ▌6▐
+ ▌▐
+
+ ▌▐
+ ▌7▐
+ ▌▐
+
+ ▌▐
+ ▌8▐
+ ▌▐
+
+ ▌▐
+ ▌9▐
+ ▌▐
@@ -24096,134 +24096,134 @@
font-weight: 700;
}
- .terminal-180633759-matrix {
+ .terminal-4257366247-matrix {
font-family: Fira Code, monospace;
font-size: 20px;
line-height: 24.4px;
font-variant-east-asian: full-width;
}
- .terminal-180633759-title {
+ .terminal-4257366247-title {
font-size: 18px;
font-weight: bold;
font-family: arial;
}
- .terminal-180633759-r1 { fill: #e1e1e1 }
- .terminal-180633759-r2 { fill: #56c278 }
- .terminal-180633759-r3 { fill: #c5c8c6 }
- .terminal-180633759-r4 { fill: #2e3339 }
- .terminal-180633759-r5 { fill: #e3e4e4 }
+ .terminal-4257366247-r1 { fill: #e1e1e1 }
+ .terminal-4257366247-r2 { fill: #56c278 }
+ .terminal-4257366247-r3 { fill: #c5c8c6 }
+ .terminal-4257366247-r4 { fill: #1d1d1d }
+ .terminal-4257366247-r5 { fill: #e3e4e4 }
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- NotifyDownScreensApp
+ NotifyDownScreensApp
-
-
-
- Screen 10▎▊
- ▎4▊
- ▎▊
-
- ▎▊
- ▎5▊
- ▎▊
-
- ▎▊
- ▎6▊
- ▎▊
-
- ▎▊
- ▎7▊
- ▎▊
-
- ▎▊
- ▎8▊
- ▎▊
-
- ▎▊
- ▎9▊
- ▎▊
+
+
+
+ Screen 10▌▐
+ ▌4▐
+ ▌▐
+
+ ▌▐
+ ▌5▐
+ ▌▐
+
+ ▌▐
+ ▌6▐
+ ▌▐
+
+ ▌▐
+ ▌7▐
+ ▌▐
+
+ ▌▐
+ ▌8▐
+ ▌▐
+
+ ▌▐
+ ▌9▐
+ ▌▐