mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
optimize focus (#2460)
* optimize focus * immediate call * update previews * snapshot
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user