Skip to content

Commit 311d49e

Browse files
authored
Merge branch 'main' into fix-push-screen-on-mount
2 parents eed200a + 49a3d7d commit 311d49e

File tree

5 files changed

+231
-1
lines changed

5 files changed

+231
-1
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 & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2101,6 +2101,41 @@ def action_toggle_console(self) -> None:
21012101
app = MRE()
21022102
assert snap_compare(app, press=["space", "space", "z"])
21032103

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

21052140
def test_updates_with_auto_refresh(snap_compare):
21062141
"""Regression test for https://github.com/Textualize/textual/issues/5056
@@ -2136,7 +2171,6 @@ def action_toggle_widget(self, widget_type: str) -> None:
21362171
app = MRE()
21372172
assert snap_compare(app, press=["z", "z"])
21382173

2139-
21402174
def test_push_screen_on_mount(snap_compare):
21412175
"""Test pushing (modal) screen immediately on mount, which was not refreshing the base screen.
21422176
@@ -2191,3 +2225,4 @@ def on_mount(self) -> None:
21912225
app = MyApp()
21922226

21932227
assert snap_compare(app)
2228+

0 commit comments

Comments
 (0)