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 538a5f5193..399287eaf9 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 +