From 718760dcb1b5356f6aa2a992cb353d5cdd2045bf Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 24 May 2023 15:53:56 +0100 Subject: [PATCH 1/3] Fix some assorted documentation cross-references Couple or so things I noticed while working on docs. --- docs/guide/screens.md | 4 ++-- src/textual/screen.py | 2 +- src/textual/widgets/_tabs.py | 17 ++++++----------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/docs/guide/screens.md b/docs/guide/screens.md index 0119ef655..25915513c 100644 --- a/docs/guide/screens.md +++ b/docs/guide/screens.md @@ -225,8 +225,8 @@ The main screen is darkened to indicate to the user that it is not active, and o It is a common requirement for screens to be able to return data. For instance, you may want a screen to show a dialog and have the result of that dialog processed *after* the screen has been popped. -To return data from a screen, call [`dismiss()`][textual.screen.dismiss] on the screen with the data you wish to return. -This will pop the screen and invoke a callback set when the screen was pushed (with [`push_screen`][textual.app.push_screen]). +To return data from a screen, call [`dismiss()`][textual.screen.Screen.dismiss] on the screen with the data you wish to return. +This will pop the screen and invoke a callback set when the screen was pushed (with [`push_screen`][textual.app.App.push_screen]). Let's modify the previous example to use `dismiss` rather than an explicit `pop_screen`. diff --git a/src/textual/screen.py b/src/textual/screen.py index 1d04c0602..796f9eb84 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -778,7 +778,7 @@ class Screen(Generic[ScreenResultType], Widget): def dismiss(self, result: ScreenResultType | Type[_NoResult] = _NoResult) -> None: """Dismiss the screen, optionally with a result. - If `result` is provided and a callback was set when the screen was [pushed][textual.app.push_screen], then + If `result` is provided and a callback was set when the screen was [pushed][textual.app.App.push_screen], then the callback will be invoked with `result`. Args: diff --git a/src/textual/widgets/_tabs.py b/src/textual/widgets/_tabs.py index 78c7a6869..c59cbca16 100644 --- a/src/textual/widgets/_tabs.py +++ b/src/textual/widgets/_tabs.py @@ -183,11 +183,6 @@ class Tabs(Widget, can_focus=True): ALLOW_SELECTOR_MATCH = {"tab"} """Additional message attributes that can be used with the [`on` decorator][textual.on].""" - tabs: Tabs - """The tabs widget containing the tab.""" - tab: Tab - """The tab that was activated.""" - def __init__(self, tabs: Tabs, tab: Tab) -> None: """Initialize event. @@ -195,8 +190,10 @@ class Tabs(Widget, can_focus=True): tabs: The Tabs widget. tab: The tab that was activated. """ - self.tabs = tabs - self.tab = tab + self.tabs: Tabs = tabs + """The tabs widget containing the tab.""" + self.tab: Tab = tab + """The tab that was activated.""" super().__init__() @property @@ -215,16 +212,14 @@ class Tabs(Widget, can_focus=True): class Cleared(Message): """Sent when there are no active tabs.""" - tabs: Tabs - """The tabs widget which was cleared.""" - def __init__(self, tabs: Tabs) -> None: """Initialize the event. Args: tabs: The tabs widget. """ - self.tabs = tabs + self.tabs: Tabs = tabs + """The tabs widget which was cleared.""" super().__init__() @property From 2c184caa2af5fca91c781f931b81cbab9b2df2db Mon Sep 17 00:00:00 2001 From: Dave Pearson Date: Wed, 24 May 2023 16:16:15 +0100 Subject: [PATCH 2/3] Fix docstring for DOMNode.set_classes The argument in the Args list was missing its name; also fixes a typo too. --- src/textual/dom.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/textual/dom.py b/src/textual/dom.py index c6efd1c4f..12e8a9537 100644 --- a/src/textual/dom.py +++ b/src/textual/dom.py @@ -1123,7 +1123,8 @@ class DOMNode(MessagePump): """Replace all classes. Args: - A string contain space separated classes, or an iterable of class names. + classes: A string containing space separated classes, or an + iterable of class names. Returns: Self. From 8151946f380acacb8daa39b9c5b705033f5263d6 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Wed, 24 May 2023 20:16:11 +0100 Subject: [PATCH 3/3] add blur (#2645) * add blur * docstring * blur on disabled * snapshot test * Add test --- CHANGELOG.md | 1 + src/textual/app.py | 2 +- src/textual/screen.py | 1 + src/textual/widget.py | 9 +- .../__snapshots__/test_snapshots.ambr | 274 ++++++++++++++---- .../snapshot_apps/blur_on_disabled.py | 20 ++ tests/snapshot_tests/test_snapshots.py | 8 + 7 files changed, 253 insertions(+), 62 deletions(-) create mode 100644 tests/snapshot_tests/snapshot_apps/blur_on_disabled.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3085106dd..f43453c25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Removed - `Placeholder.reset_color_cycle` +- Removed `Widget.reset_focus` (now called `Widget.blur`) https://github.com/Textualize/textual/issues/2642 ## [0.26.0] - 2023-05-20 diff --git a/src/textual/app.py b/src/textual/app.py index aca3be101..fe30f2e1b 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -2075,7 +2075,7 @@ class App(Generic[ReturnType], DOMNode): Args: widget: A Widget to unregister """ - widget.reset_focus() + widget.blur() if isinstance(widget._parent, Widget): widget._parent._nodes._remove(widget) widget._detach() diff --git a/src/textual/screen.py b/src/textual/screen.py index 796f9eb84..29c6ab95b 100644 --- a/src/textual/screen.py +++ b/src/textual/screen.py @@ -386,6 +386,7 @@ class Screen(Generic[ScreenResultType], Widget): focusable_widgets = self.focus_chain if not focusable_widgets: # If there's nothing to focus... give up now. + self.set_focus(None) return try: diff --git a/src/textual/widget.py b/src/textual/widget.py index d1e7bc64f..a67c3fe27 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -2712,6 +2712,7 @@ class Widget(DOMNode): def watch_disabled(self) -> None: """Update the styles of the widget and its children when disabled is toggled.""" + self.blur() self._update_styles() def _size_updated( @@ -3017,8 +3018,10 @@ class Widget(DOMNode): self.app.call_later(set_focus, self) return self - def reset_focus(self) -> Self: - """Reset the focus (move it to the next available widget). + def blur(self) -> Self: + """Blur (un-focus) the widget. + + Focus will be moved to the next available widget in the focus chain.. Returns: The `Widget` instance. @@ -3172,7 +3175,7 @@ class Widget(DOMNode): def _on_hide(self, event: events.Hide) -> None: if self.has_focus: - self.reset_focus() + self.blur() def _on_scroll_to_region(self, message: messages.ScrollToRegion) -> None: self.scroll_to_region(message.region, animate=True) diff --git a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr index e39be06d6..7013a574a 100644 --- a/tests/snapshot_tests/__snapshots__/test_snapshots.ambr +++ b/tests/snapshot_tests/__snapshots__/test_snapshots.ambr @@ -709,6 +709,164 @@ ''' # --- +# name: test_blur_on_disabled + ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BlurApp + + + + + + + + + + ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ + foo  + ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ + + + + + + + + + + + + + + + + + + + + + + + + + + ''' +# --- # name: test_border_alpha ''' @@ -22270,134 +22428,134 @@ font-weight: 700; } - .terminal-2821039707-matrix { + .terminal-1349433035-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-2821039707-title { + .terminal-1349433035-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-2821039707-r1 { fill: #004578 } - .terminal-2821039707-r2 { fill: #c5c8c6 } - .terminal-2821039707-r3 { fill: #e1e1e1 } - .terminal-2821039707-r4 { fill: #23568b } + .terminal-1349433035-r1 { fill: #004578 } + .terminal-1349433035-r2 { fill: #c5c8c6 } + .terminal-1349433035-r3 { fill: #e1e1e1 } + .terminal-1349433035-r4 { fill: #14191f } - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - MyApp + MyApp - - - - ────────────────────────────────────────────────────────────────────────────── - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM - SPAM▇▇ - SPAM - ────────────────────────────────────────────────────────────────────────────── + + + + ────────────────────────────────────────────────────────────────────────────── + SPAM + SPAM▁▁ + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + SPAM + ────────────────────────────────────────────────────────────────────────────── diff --git a/tests/snapshot_tests/snapshot_apps/blur_on_disabled.py b/tests/snapshot_tests/snapshot_apps/blur_on_disabled.py new file mode 100644 index 000000000..3bfa0e122 --- /dev/null +++ b/tests/snapshot_tests/snapshot_apps/blur_on_disabled.py @@ -0,0 +1,20 @@ +from textual.app import App, ComposeResult +from textual.widgets import Input + + +class BlurApp(App): + BINDINGS = [("f3", "disable")] + + def compose(self) -> ComposeResult: + yield Input() + + def on_ready(self) -> None: + self.query_one(Input).focus() + + def action_disable(self) -> None: + self.query_one(Input).disabled = True + + +if __name__ == "__main__": + app = BlurApp() + app.run() diff --git a/tests/snapshot_tests/test_snapshots.py b/tests/snapshot_tests/test_snapshots.py index 874f096d9..fd22c0ca3 100644 --- a/tests/snapshot_tests/test_snapshots.py +++ b/tests/snapshot_tests/test_snapshots.py @@ -514,3 +514,11 @@ def test_select_rebuild(snap_compare): SNAPSHOT_APPS_DIR / "select_rebuild.py", press=["space", "escape", "tab", "enter", "tab", "space"], ) + + +def test_blur_on_disabled(snap_compare): + # https://github.com/Textualize/textual/issues/2641 + assert snap_compare( + SNAPSHOT_APPS_DIR / "blur_on_disabled.py", + press=[*"foo", "f3", *"this should not appear"], + )