|
| 1 | +from textual.app import App, ComposeResult |
| 2 | +from textual.containers import Grid |
| 3 | +from textual.screen import ModalScreen |
| 4 | +from textual.widgets import Button, Footer, Header, Label |
| 5 | + |
| 6 | +TEXT = """I must not fear. |
| 7 | +Fear is the mind-killer. |
| 8 | +Fear is the little-death that brings total obliteration. |
| 9 | +I will face my fear. |
| 10 | +I will permit it to pass over me and through me. |
| 11 | +And when it has gone past, I will turn the inner eye to see its path. |
| 12 | +Where the fear has gone there will be nothing. Only I will remain.""" |
| 13 | + |
| 14 | + |
| 15 | +class QuitScreen(ModalScreen[bool]): # (1)! |
| 16 | + """Screen with a dialog to quit.""" |
| 17 | + |
| 18 | + def compose(self) -> ComposeResult: |
| 19 | + yield Grid( |
| 20 | + Label("Are you sure you want to quit?", id="question"), |
| 21 | + Button("Quit", variant="error", id="quit"), |
| 22 | + Button("Cancel", variant="primary", id="cancel"), |
| 23 | + id="dialog", |
| 24 | + ) |
| 25 | + |
| 26 | + def on_button_pressed(self, event: Button.Pressed) -> None: |
| 27 | + if event.button.id == "quit": |
| 28 | + self.dismiss(True) |
| 29 | + else: |
| 30 | + self.dismiss(False) |
| 31 | + |
| 32 | + |
| 33 | +class ModalApp(App): |
| 34 | + """An app with a modal dialog.""" |
| 35 | + |
| 36 | + BINDINGS = [("q", "request_quit", "Quit")] |
| 37 | + |
| 38 | + CSS = """ |
| 39 | +QuitScreen { |
| 40 | + align: center middle; |
| 41 | +} |
| 42 | +
|
| 43 | +#dialog { |
| 44 | + grid-size: 2; |
| 45 | + grid-gutter: 1 2; |
| 46 | + grid-rows: 1fr 3; |
| 47 | + padding: 0 1; |
| 48 | + width: 60; |
| 49 | + height: 11; |
| 50 | + border: thick $background 80%; |
| 51 | + background: $surface; |
| 52 | +} |
| 53 | +
|
| 54 | +#question { |
| 55 | + column-span: 2; |
| 56 | + height: 1fr; |
| 57 | + width: 1fr; |
| 58 | + content-align: center middle; |
| 59 | +} |
| 60 | +
|
| 61 | +Button { |
| 62 | + width: 100%; |
| 63 | +} |
| 64 | +
|
| 65 | + """ |
| 66 | + |
| 67 | + def compose(self) -> ComposeResult: |
| 68 | + yield Header() |
| 69 | + yield Label(TEXT * 8) |
| 70 | + yield Footer() |
| 71 | + |
| 72 | + def action_request_quit(self) -> None: |
| 73 | + """Action to display the quit dialog.""" |
| 74 | + |
| 75 | + def check_quit(quit: bool) -> None: |
| 76 | + """Called when QuitScreen is dismissed.""" |
| 77 | + |
| 78 | + if quit: |
| 79 | + self.exit() |
| 80 | + |
| 81 | + self.push_screen(QuitScreen(), check_quit) |
| 82 | + |
| 83 | + |
| 84 | +async def test_modal_pop_screen(): |
| 85 | + # https://github.com/Textualize/textual/issues/4656 |
| 86 | + |
| 87 | + async with ModalApp().run_test() as pilot: |
| 88 | + await pilot.pause() |
| 89 | + # Check clicking the footer brings up the quit screen |
| 90 | + await pilot.click(Footer) |
| 91 | + assert isinstance(pilot.app.screen, QuitScreen) |
| 92 | + # Check activating the quit button exits the app |
| 93 | + await pilot.press("enter") |
| 94 | + assert pilot.app._exit |
0 commit comments