Skip to content

Commit 49a3d7d

Browse files
authored
Merge pull request #5069 from Textualize/pop-screen-until
Add pop_until_active
2 parents 112355e + 0f2f6c7 commit 49a3d7d

File tree

5 files changed

+231
-0
lines changed

5 files changed

+231
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2323

2424
- Added `x_axis` and `y_axis` parameters to `Widget.scroll_to_region` https://github.com/Textualize/textual/pull/5047
2525
- Added `Tree.move_cursor_to_line` https://github.com/Textualize/textual/pull/5052
26+
- Added `Screen.pop_until_active` https://github.com/Textualize/textual/pull/5069
2627

2728
### Changed
2829

src/textual/app.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,6 +2636,33 @@ async def do_pop() -> None:
26362636

26372637
return AwaitComplete(do_pop()).call_next(self)
26382638

2639+
def _pop_to_screen(self, screen: Screen) -> None:
2640+
"""Pop screens until the given screen is active.
2641+
2642+
Args:
2643+
screen: desired active screen
2644+
2645+
Raises:
2646+
ScreenError: If the screen doesn't exist in the stack.
2647+
"""
2648+
screens_to_pop: list[Screen] = []
2649+
for pop_screen in reversed(self.screen_stack):
2650+
if pop_screen is not screen:
2651+
screens_to_pop.append(pop_screen)
2652+
else:
2653+
break
2654+
else:
2655+
raise ScreenError(f"Screen {screen!r} not in screen stack")
2656+
2657+
async def pop_screens() -> None:
2658+
"""Pop any screens in `screens_to_pop`."""
2659+
with self.batch_update():
2660+
for screen in screens_to_pop:
2661+
await screen.dismiss()
2662+
2663+
if screens_to_pop:
2664+
self.call_later(pop_screens)
2665+
26392666
def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None:
26402667
"""Focus (or unfocus) a widget. A focused widget will receive key events first.
26412668

src/textual/screen.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,23 @@ def pre_await() -> None:
14261426

14271427
return await_pop
14281428

1429+
def pop_until_active(self) -> None:
1430+
"""Pop any screens on top of this one, until this screen is active.
1431+
1432+
Raises:
1433+
ScreenError: If this screen is not in the current mode.
1434+
1435+
"""
1436+
from textual.app import ScreenError
1437+
1438+
try:
1439+
self.app._pop_to_screen(self)
1440+
except ScreenError:
1441+
# More specific error message
1442+
raise ScreenError(
1443+
f"Can't make {self} active as it is not in the current stack."
1444+
) from None
1445+
14291446
async def action_dismiss(self, result: ScreenResultType | None = None) -> None:
14301447
"""A wrapper around [`dismiss`][textual.screen.Screen.dismiss] that can be called as an action.
14311448
Lines changed: 150 additions & 0 deletions
Loading

tests/snapshot_tests/test_snapshots.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,6 +2100,41 @@ def action_toggle_console(self) -> None:
21002100
app = MRE()
21012101
assert snap_compare(app, press=["space", "space", "z"])
21022102

2103+
def test_pop_until_active(snap_compare):
2104+
"""End result should be screen showing 'BASE'"""
2105+
2106+
class BaseScreen(Screen):
2107+
def compose(self) -> ComposeResult:
2108+
yield Label("BASE")
2109+
2110+
class FooScreen(Screen):
2111+
def compose(self) -> ComposeResult:
2112+
yield Label("Foo")
2113+
2114+
class BarScreen(Screen):
2115+
BINDINGS = [("b", "app.make_base_active")]
2116+
2117+
def compose(self) -> ComposeResult:
2118+
yield Label("Bar")
2119+
2120+
class PopApp(App):
2121+
SCREENS = {"base": BaseScreen}
2122+
2123+
async def on_mount(self) -> None:
2124+
# Push base
2125+
await self.push_screen("base")
2126+
# Push two screens
2127+
await self.push_screen(FooScreen())
2128+
await self.push_screen(BarScreen())
2129+
2130+
def action_make_base_active(self) -> None:
2131+
self.get_screen("base").pop_until_active()
2132+
2133+
app = PopApp()
2134+
# App will push three screens
2135+
# Pressing "b" will call pop_until_active, and pop two screens
2136+
# End result should be screen showing "BASE"
2137+
assert snap_compare(app, press=["b"])
21032138

21042139
def test_updates_with_auto_refresh(snap_compare):
21052140
"""Regression test for https://github.com/Textualize/textual/issues/5056
@@ -2134,3 +2169,4 @@ def action_toggle_widget(self, widget_type: str) -> None:
21342169

21352170
app = MRE()
21362171
assert snap_compare(app, press=["z", "z"])
2172+

0 commit comments

Comments
 (0)