Skip to content

Commit c7b357d

Browse files
authored
Merge pull request #4621 from Textualize/button-dismiss
Button dismiss
2 parents 1f34562 + 4355e23 commit c7b357d

File tree

5 files changed

+34
-13
lines changed

5 files changed

+34
-13
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## Unreleased
99

10-
### Changed
10+
### [0.66.0]
1111

1212
- `get_content_height` will now return 0 if the renderable is Falsey https://github.com/Textualize/textual/pull/4617
13+
- Buttons may not be pressed within their "active_effect_duration" to prevent inadvertent activations https://github.com/Textualize/textual/pull/4621
14+
- `Screen.dismiss` is now a noop if the screen isn't active. Previously it would raise a `ScreenStackError`, now it returns `False`. https://github.com/Textualize/textual/pull/4621
15+
16+
### Added
17+
18+
- Added `Screen.is_active`
1319

1420
## [0.65.2] - 2023-06-06
1521

@@ -2085,6 +2091,7 @@ https://textual.textualize.io/blog/2022/11/08/version-040/#version-040
20852091
- New handler system for messages that doesn't require inheritance
20862092
- Improved traceback handling
20872093

2094+
[0.66.0]: https://github.com/Textualize/textual/compare/v0.65.2...v0.66.0
20882095
[0.65.2]: https://github.com/Textualize/textual/compare/v0.65.1...v0.65.2
20892096
[0.65.1]: https://github.com/Textualize/textual/compare/v0.65.0...v0.65.1
20902097
[0.65.0]: https://github.com/Textualize/textual/compare/v0.64.0...v0.65.0

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "textual"
3-
version = "0.65.2"
3+
version = "0.66.0"
44
homepage = "https://github.com/Textualize/textual"
55
repository = "https://github.com/Textualize/textual"
66
documentation = "https://textual.textualize.io/"

src/textual/screen.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,14 @@ def active_bindings(self) -> dict[str, ActiveBinding]:
342342

343343
return bindings_map
344344

345+
@property
346+
def is_active(self) -> bool:
347+
"""Is the screen active (i.e. visible and top of the stack)?"""
348+
try:
349+
return self.app.screen is self
350+
except Exception:
351+
return False
352+
345353
def render(self) -> RenderableType:
346354
"""Render method inherited from widget, used to render the screen's background.
347355
@@ -1215,29 +1223,34 @@ def _forward_event(self, event: events.Event) -> None:
12151223
class _NoResult:
12161224
"""Class used to mark that there is no result."""
12171225

1218-
def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> None:
1226+
def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> bool:
12191227
"""Dismiss the screen, optionally with a result.
12201228
1229+
!!! note
1230+
1231+
Only the active screen may be dismissed. If you try to dismiss a screen that isn't active,
1232+
this method will return `False`.
1233+
12211234
If `result` is provided and a callback was set when the screen was [pushed][textual.app.App.push_screen], then
12221235
the callback will be invoked with `result`.
12231236
12241237
Args:
12251238
result: The optional result to be passed to the result callback.
12261239
1240+
Returns:
1241+
`True` if the Screen was dismissed, or `False` if the Screen wasn't dismissed due to not being active.
1242+
12271243
Raises:
12281244
ScreenStackError: If trying to dismiss a screen that is not at the top of
12291245
the stack.
12301246
12311247
"""
1232-
if self is not self.app.screen:
1233-
from .app import ScreenStackError
1234-
1235-
raise ScreenStackError(
1236-
f"Can't dismiss screen {self} that's not at the top of the stack."
1237-
)
1248+
if not self.is_active:
1249+
return False
12381250
if result is not self._NoResult and self._result_callbacks:
12391251
self._result_callbacks[-1](cast(ScreenResultType, result))
12401252
self.app.pop_screen()
1253+
return True
12411254

12421255
def action_dismiss(
12431256
self, result: ScreenResultType | Type[_NoResult] = _NoResult

src/textual/widgets/_button.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ def post_render(self, renderable: RenderableType) -> ConsoleRenderable:
250250

251251
async def _on_click(self, event: events.Click) -> None:
252252
event.stop()
253-
self.press()
253+
if not self.has_class("-active"):
254+
self.press()
254255

255256
def press(self) -> Self:
256257
"""Animate the button and send the [Pressed][textual.widgets.Button.Pressed] message.
@@ -278,7 +279,8 @@ def _start_active_affect(self) -> None:
278279

279280
def action_press(self) -> None:
280281
"""Activate a press of the button."""
281-
self.press()
282+
if not self.has_class("-active"):
283+
self.press()
282284

283285
@classmethod
284286
def success(

tests/test_screens.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ async def key_p(self) -> None:
300300
app = MyApp()
301301
async with app.run_test() as pilot:
302302
await pilot.press("p")
303-
with pytest.raises(ScreenStackError):
304-
app.bottom.dismiss()
303+
assert not app.bottom.dismiss()
305304

306305

307306
async def test_dismiss_action():

0 commit comments

Comments
 (0)