optimize focus (#2460)

* optimize focus

* immediate call

* update previews

* snapshot
This commit is contained in:
Will McGugan
2023-05-03 11:48:56 +01:00
committed by GitHub
parent 90d9693168
commit 41dbc66b23
8 changed files with 118 additions and 91 deletions

View File

@@ -1253,6 +1253,7 @@ class App(Generic[ReturnType], DOMNode):
will be added, and this method is called to apply the corresponding
:hover styles.
"""
descendants = node.walk_children(with_self=True)
self.stylesheet.update_nodes(descendants, animate=True)

View File

@@ -1,5 +1,5 @@
from textual.app import App, ComposeResult
from textual.containers import VerticalScroll
from textual.containers import Vertical
from textual.css.constants import VALID_BORDER
from textual.widgets import Button, Label
@@ -12,7 +12,7 @@ And when it has gone past, I will turn the inner eye to see its path.
Where the fear has gone there will be nothing. Only I will remain."""
class BorderButtons(VerticalScroll):
class BorderButtons(Vertical):
DEFAULT_CSS = """
BorderButtons {
dock: left;

View File

@@ -1,5 +1,5 @@
from textual.app import App, ComposeResult
from textual.containers import Horizontal, VerticalScroll
from textual.containers import Horizontal, Vertical, VerticalScroll
from textual.design import ColorSystem
from textual.widget import Widget
from textual.widgets import Button, Footer, Label, Static
@@ -20,11 +20,11 @@ class ColorItem(Horizontal):
pass
class ColorGroup(VerticalScroll):
class ColorGroup(Vertical):
pass
class Content(VerticalScroll):
class Content(Vertical):
pass

View File

@@ -5,7 +5,7 @@ from rich.console import RenderableType
from textual._easing import EASING
from textual.app import App, ComposeResult
from textual.cli.previews.borders import TEXT
from textual.containers import Container, Horizontal, VerticalScroll
from textual.containers import Horizontal, Vertical
from textual.reactive import reactive, var
from textual.scrollbar import ScrollBarRender
from textual.widget import Widget
@@ -72,13 +72,13 @@ class EasingApp(App):
)
yield EasingButtons()
with VerticalScroll():
with Vertical():
with Horizontal(id="inputs"):
yield Label("Animation Duration:", id="label")
yield duration_input
with Horizontal():
yield self.animated_bar
yield VerticalScroll(self.opacity_widget, id="other")
yield Vertical(self.opacity_widget, id="other")
yield Footer()
def on_button_pressed(self, event: Button.Pressed) -> None:

View File

@@ -31,6 +31,7 @@ from .binding import Binding
from .css.match import match
from .css.parse import parse_selectors
from .css.query import QueryType
from .dom import DOMNode
from .geometry import Offset, Region, Size
from .reactive import Reactive
from .renderables.background_screen import BackgroundScreen
@@ -404,6 +405,32 @@ class Screen(Generic[ScreenResultType], Widget):
# Go with the what was found.
self.set_focus(chosen)
def _update_focus_styles(
self, focused: Widget | None = None, blurred: Widget | None = None
) -> None:
"""Update CSS for focus changes.
Args:
focused: The widget that was focused.
blurred: The widget that was blurred.
"""
widgets: set[DOMNode] = set()
if focused is not None:
for widget in reversed(focused.ancestors_with_self):
if widget._has_focus_within:
widgets.update(widget.walk_children(with_self=True))
break
if blurred is not None:
for widget in reversed(blurred.ancestors_with_self):
if widget._has_focus_within:
widgets.update(widget.walk_children(with_self=True))
break
if widgets:
self.app.stylesheet.update_nodes(
[widget for widget in widgets if widget._has_focus_within], animate=True
)
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.
@@ -415,25 +442,35 @@ class Screen(Generic[ScreenResultType], Widget):
# Widget is already focused
return
focused: Widget | None = None
blurred: Widget | None = None
if widget is None:
# No focus, so blur currently focused widget if it exists
if self.focused is not None:
self.focused.post_message(events.Blur())
self.focused = None
blurred = self.focused
self.log.debug("focus was removed")
elif widget.focusable:
if self.focused != widget:
if self.focused is not None:
# Blur currently focused widget
self.focused.post_message(events.Blur())
blurred = self.focused
# Change focus
self.focused = widget
# Send focus event
if scroll_visible:
self.screen.scroll_to_widget(widget)
widget.post_message(events.Focus())
focused = widget
self._update_focus_styles(self.focused, widget)
self.log.debug(widget, "was focused")
self._update_focus_styles(focused, blurred)
async def _on_idle(self, event: events.Idle) -> None:
# Check for any widgets marked as 'dirty' (needs a repaint)
event.prevent_default()

View File

@@ -3077,21 +3077,11 @@ class Widget(DOMNode):
def _on_focus(self, event: events.Focus) -> None:
self.has_focus = True
self.refresh()
for widget in reversed(self.ancestors_with_self):
if widget._has_focus_within:
widget._update_styles()
break
self.post_message(events.DescendantFocus())
def _on_blur(self, event: events.Blur) -> None:
self.has_focus = False
self.refresh()
for widget in reversed(self.ancestors_with_self):
if widget._has_focus_within:
widget._update_styles()
break
self.post_message(events.DescendantBlur())
def _on_mouse_scroll_down(self, event: events.MouseScrollDown) -> None:

File diff suppressed because one or more lines are too long

View File

@@ -294,7 +294,7 @@ def test_programmatic_scrollbar_gutter_change(snap_compare):
def test_borders_preview(snap_compare):
assert snap_compare(CLI_PREVIEWS_DIR / "borders.py", press=["tab", "tab", "enter"])
assert snap_compare(CLI_PREVIEWS_DIR / "borders.py", press=["tab", "enter"])
def test_colors_preview(snap_compare):