From d0ed7ef826c609e71e12466d4f4e2ee2897d784e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 5 May 2022 14:46:20 +0100 Subject: [PATCH 1/2] CSS focus-within --- sandbox/uber.css | 10 +++++++++- sandbox/uber.py | 12 +++++++----- src/textual/app.py | 3 +++ src/textual/events.py | 8 ++++++++ src/textual/widget.py | 11 +++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) diff --git a/sandbox/uber.css b/sandbox/uber.css index 517aec8d2..14848e9c2 100644 --- a/sandbox/uber.css +++ b/sandbox/uber.css @@ -1,3 +1,7 @@ +App.-show-focus *:focus { + tint: #8bc34a 50%; +} + #uber1 { layout: vertical; background: green; @@ -5,8 +9,12 @@ border: heavy white; } +#uber1:focus-within { + background: darkslateblue; +} + .list-item { - height: 8; + height: 10; color: #12a0; background: #ffffff00; } diff --git a/sandbox/uber.py b/sandbox/uber.py index 8363866cb..76dfebeb9 100644 --- a/sandbox/uber.py +++ b/sandbox/uber.py @@ -15,15 +15,16 @@ class BasicApp(App): self.bind("d", "dump") self.bind("t", "log_tree") self.bind("p", "print") - self.bind("o", "toggle_visibility") - self.bind("p", "toggle_display") + self.bind("v", "toggle_visibility") + self.bind("x", "toggle_display") self.bind("f", "modify_focussed") self.bind("b", "toggle_border") async def on_mount(self): """Build layout here.""" - + first_child = Placeholder(id="child1", classes="list-item") uber1 = Widget( + first_child, Placeholder(id="child1", classes="list-item"), Placeholder(id="child2", classes="list-item"), Placeholder(id="child3", classes="list-item"), @@ -32,6 +33,8 @@ class BasicApp(App): Placeholder(classes="list-item"), ) self.mount(uber1=uber1) + self.first_child = first_child + self.uber = uber1 async def on_key(self, event: events.Key) -> None: await self.dispatch_key(event) @@ -51,8 +54,7 @@ class BasicApp(App): self.screen.tree, sep=" - ", ) - print(1234, 5678) - sys.stdout.write("abcdef") + self.app.set_focus(None) def action_modify_focussed(self): """Increment height of focussed child, randomise border and bg color""" diff --git a/src/textual/app.py b/src/textual/app.py index 6192e458f..02d9d3b26 100644 --- a/src/textual/app.py +++ b/src/textual/app.py @@ -536,16 +536,19 @@ class App(Generic[ReturnType], DOMNode): # No focus, so blur currently focused widget if it exists if self.focused is not None: self.focused.post_message_no_wait(events.Blur(self)) + self.focused.emit_no_wait(events.DescendantBlur(self)) self.focused = None elif widget.can_focus: if self.focused != widget: if self.focused is not None: # Blur currently focused widget self.focused.post_message_no_wait(events.Blur(self)) + self.focused.emit_no_wait(events.DescendantBlur(self)) # Change focus self.focused = widget # Send focus event widget.post_message_no_wait(events.Focus(self)) + widget.emit_no_wait(events.DescendantFocus(self)) async def _set_mouse_over(self, widget: Widget | None) -> None: """Called when the mouse is over another widget. diff --git a/src/textual/events.py b/src/textual/events.py index 5525a123a..20bf7033a 100644 --- a/src/textual/events.py +++ b/src/textual/events.py @@ -393,3 +393,11 @@ class Focus(Event, bubble=False): class Blur(Event, bubble=False): pass + + +class DescendantFocus(Event, bubble=True): + pass + + +class DescendantBlur(Event, bubble=True): + pass diff --git a/src/textual/widget.py b/src/textual/widget.py index 7bd885d9c..f7bbc6df7 100644 --- a/src/textual/widget.py +++ b/src/textual/widget.py @@ -98,6 +98,7 @@ class Widget(DOMNode): auto_width = Reactive(True) auto_height = Reactive(True) has_focus = Reactive(False) + descendant_has_focus = Reactive(False) mouse_over = Reactive(False) scroll_x = Reactive(0.0, repaint=False) scroll_y = Reactive(0.0, repaint=False) @@ -424,6 +425,8 @@ class Widget(DOMNode): yield "hover" if self.has_focus: yield "focus" + if self.descendant_has_focus: + yield "focus-within" def watch(self, attribute_name, callback: Callable[[Any], Awaitable[None]]) -> None: watch(self, attribute_name, callback) @@ -721,11 +724,19 @@ class Widget(DOMNode): self.mouse_over = True def on_focus(self, event: events.Focus) -> None: + self.emit_no_wait(events.DescendantFocus(self)) self.has_focus = True def on_blur(self, event: events.Blur) -> None: + self.emit_no_wait(events.DescendantBlur(self)) self.has_focus = False + def on_descendant_focus(self, event: events.DescendantFocus) -> None: + self.descendant_has_focus = True + + def on_descendant_blur(self, event: events.DescendantBlur) -> None: + self.descendant_has_focus = False + def on_mouse_scroll_down(self, event) -> None: if self.is_container: self.scroll_down(animate=False) From 84d34dd4ffd32d3665e85c80784c546bab341c07 Mon Sep 17 00:00:00 2001 From: Will McGugan Date: Thu, 5 May 2022 16:21:49 +0100 Subject: [PATCH 2/2] diplay event method --- src/textual/message_pump.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/textual/message_pump.py b/src/textual/message_pump.py index 4da07f2c8..66ed98721 100644 --- a/src/textual/message_pump.py +++ b/src/textual/message_pump.py @@ -246,7 +246,9 @@ class MessagePump: if self._message_queue.empty(): if not self._closed: event = events.Idle(self) - for method in self._get_dispatch_methods("on_idle", event): + for _cls, method in self._get_dispatch_methods( + "on_idle", event + ): await invoke(method, event) log("CLOSED", self) @@ -264,18 +266,25 @@ class MessagePump: def _get_dispatch_methods( self, method_name: str, message: Message - ) -> Iterable[Callable[[Message], Awaitable]]: + ) -> Iterable[tuple[type, Callable[[Message], Awaitable]]]: for cls in self.__class__.__mro__: if message._no_default_action: break method = cls.__dict__.get(method_name, None) if method is not None: - yield method.__get__(self, cls) + yield cls, method.__get__(self, cls) async def on_event(self, event: events.Event) -> None: _rich_traceback_guard = True - for method in self._get_dispatch_methods(f"on_{event.name}", event): - log(event, ">>>", self, verbosity=event.verbosity) + + for cls, method in self._get_dispatch_methods(f"on_{event.name}", event): + log( + event, + ">>>", + self, + f"method=<{cls.__name__}.{method.__func__.__name__}>", + verbosity=event.verbosity, + ) await invoke(method, event) if event.bubble and self._parent and not event._stop_propagation: