From d0ed7ef826c609e71e12466d4f4e2ee2897d784e Mon Sep 17 00:00:00 2001 From: Darren Burns Date: Thu, 5 May 2022 14:46:20 +0100 Subject: [PATCH] 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)