-
Notifications
You must be signed in to change notification settings - Fork 827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Try and better settle focus after a focused widget is removed (redux) #954
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
all: | ||
poetry run textual run --dev focus_removal_tester.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
"""Focus removal tester. | ||
|
||
https://github.com/Textualize/textual/issues/939 | ||
""" | ||
|
||
from textual.app import App | ||
from textual.containers import Container | ||
from textual.widgets import Static, Header, Footer, Button | ||
|
||
|
||
class LeftButton(Button): | ||
pass | ||
|
||
|
||
class RightButton(Button): | ||
pass | ||
|
||
|
||
class NonFocusParent(Static): | ||
def compose(self): | ||
yield LeftButton("Do Not Press") | ||
yield Static("Test") | ||
yield RightButton("Really Do Not Press") | ||
|
||
|
||
class FocusRemovalTester(App[None]): | ||
|
||
BINDINGS = [("a", "add_widget", "Add Widget"), ("d", "del_widget", "Delete Widget")] | ||
|
||
def compose(self): | ||
yield Header() | ||
yield Container() | ||
yield Footer() | ||
|
||
def action_add_widget(self): | ||
self.query_one(Container).mount(NonFocusParent()) | ||
|
||
def action_del_widget(self): | ||
candidates = self.query(NonFocusParent) | ||
if candidates: | ||
candidates.last().remove() | ||
|
||
|
||
if __name__ == "__main__": | ||
FocusRemovalTester().run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
from ._compositor import Compositor, MapGeometry | ||
from .timer import Timer | ||
from ._types import CallbackType | ||
from .dom import DOMNode | ||
from .geometry import Offset, Region, Size | ||
from .reactive import Reactive | ||
from .renderables.blank import Blank | ||
|
@@ -224,19 +225,51 @@ def focus_previous(self) -> Widget | None: | |
""" | ||
return self._move_focus(-1) | ||
|
||
def _reset_focus(self, widget: Widget) -> None: | ||
def _reset_focus( | ||
self, widget: Widget, avoiding: list[DOMNode] | None = None | ||
) -> None: | ||
"""Reset the focus when a widget is removed | ||
|
||
Args: | ||
widget (Widget): A widget that is removed. | ||
avoiding (list[DOMNode] | None, optional): Optional list of nodes to avoid. | ||
""" | ||
if self.focused is widget: | ||
for sibling in widget.siblings: | ||
if sibling.can_focus: | ||
sibling.focus() | ||
break | ||
else: | ||
self.focused = None | ||
|
||
avoiding = avoiding or [] | ||
|
||
# Make this a NOP if we're being asked to deal with a widget that | ||
# isn't actually the currently-focused widget. | ||
if self.focused is not widget: | ||
return | ||
|
||
# Grab the list of widgets that we can set focus to. | ||
focusable_widgets = self.focus_chain | ||
if not focusable_widgets: | ||
# If there's nothing to focus... give up now. | ||
return | ||
|
||
try: | ||
# Find the location of the widget we're taking focus from, in | ||
# the focus chain. | ||
widget_index = focusable_widgets.index(widget) | ||
except ValueError: | ||
# Seems we can't find it. There's no good reason this should | ||
# happen but, on the off-chance, let's go into a "no focus" state. | ||
self.set_focus(None) | ||
return | ||
|
||
# Now go looking for something before it, that isn't about to be | ||
# removed, and which can receive focus, and go focus that. | ||
chosen: DOMNode | None = None | ||
for candidate in reversed( | ||
focusable_widgets[widget_index + 1 :] + focusable_widgets[:widget_index] | ||
): | ||
if candidate not in avoiding and candidate.can_focus: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that we can be sure that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need a 🤦 as the real response here. |
||
chosen = candidate | ||
break | ||
|
||
# Go with the what was found. | ||
self.set_focus(chosen) | ||
|
||
def set_focus(self, widget: Widget | None, scroll_visible: bool = True) -> None: | ||
"""Focus (or un-focus) a widget. A focused widget will receive key events first. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove the
list
when the other PR is merged.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aye, already noted in 11ddcdd on commit.