update footer on focus change

This commit is contained in:
Will McGugan
2022-09-09 09:27:46 +01:00
parent 48546112f8
commit 528c617b99
8 changed files with 105 additions and 14 deletions

53
sandbox/will/bindings.py Normal file
View File

@@ -0,0 +1,53 @@
from textual.app import App, ComposeResult
from textual.widgets import Footer, Static
class Focusable(Static, can_focus=True):
DEFAULT_CSS = """
Focusable {
background: blue 20%;
height: 1fr;
padding: 1;
}
Focusable:hover {
outline: solid white;
}
Focusable:focus {
background: red 20%;
}
"""
class Focusable1(Focusable):
BINDINGS = [
("a", "app.bell", "Ding"),
]
def render(self) -> str:
return repr(self)
class Focusable2(Focusable):
CSS = ""
BINDINGS = [
("b", "app.bell", "Beep"),
]
def render(self) -> str:
return repr(self)
class BindingApp(App):
BINDINGS = [("f1", "app.bell", "Bell")]
def compose(self) -> ComposeResult:
yield Focusable1()
yield Focusable2()
yield Footer()
app = BindingApp()
if __name__ == "__main__":
app.run()

View File

@@ -154,6 +154,8 @@ class App(Generic[ReturnType], DOMNode):
CSS_PATH: str | None = None
focused: Reactive[Widget | None] = Reactive(None)
def __init__(
self,
driver_class: Type[Driver] | None = None,
@@ -181,7 +183,6 @@ class App(Generic[ReturnType], DOMNode):
self._screen_stack: list[Screen] = []
self._sync_available = False
self.focused: Widget | None = None
self.mouse_over: Widget | None = None
self.mouse_captured: Widget | None = None
self._driver: Driver | None = None

View File

@@ -12,7 +12,11 @@ else: # pragma: no cover
from typing_extensions import TypeAlias
BindingType: TypeAlias = "Binding | tuple[str, str] | tuple[str, str, str]"
BindingType: TypeAlias = "Binding | tuple[str, str, str]"
class BindingError(Exception):
"""A binding related error."""
class NoBinding(Exception):
@@ -39,6 +43,10 @@ class Bindings:
if isinstance(binding, Binding):
yield binding
else:
if len(binding) != 3:
raise BindingError(
f"BINDINGS must contain a tuple of three strings, not {binding!r}"
)
yield Binding(*binding)
self.keys: MutableMapping[str, Binding] = (

View File

@@ -69,7 +69,8 @@ def import_app(import_name: str) -> App:
except Exception as error:
raise AppFail(str(error))
global_vars["sys"].argv = [path, *argv]
if "sys" in global_vars:
global_vars["sys"].argv = [path, *argv]
if name:
# User has given a name, use that

View File

@@ -3,6 +3,8 @@ from __future__ import annotations
from functools import partial
from inspect import isawaitable
from typing import TYPE_CHECKING, Any, Callable, Generic, Type, TypeVar, Union
from weakref import WeakSet
from . import events
from ._callback import count_parameters, invoke
@@ -191,7 +193,7 @@ def watch(
watcher_name = f"__{attribute_name}_watchers"
current_value = getattr(obj, attribute_name, None)
if not hasattr(obj, watcher_name):
setattr(obj, watcher_name, set())
setattr(obj, watcher_name, WeakSet())
watchers = getattr(obj, watcher_name)
watchers.add(callback)
Reactive._check_watchers(obj, attribute_name, current_value)

View File

@@ -117,6 +117,7 @@ class Widget(DOMNode):
self._arrangement_cache_key: tuple[int, Size] = (-1, Size())
self._styles_cache = StylesCache()
self._rich_style_cache: dict[str, Style] = {}
self._lock = Lock()
@@ -188,6 +189,21 @@ class Widget(DOMNode):
self.allow_horizontal_scroll or self.allow_vertical_scroll
)
def get_component_rich_style(self, name: str) -> Style:
"""Get a *Rich* style for a component.
Args:
name (str): Name of component.
Returns:
Style: A Rich style object.
"""
style = self._rich_style_cache.get(name)
if style is None:
style = self.get_component_styles(name).rich_style
self._rich_style_cache[name] = style
return style
def _arrange(self, size: Size) -> DockArrangeResult:
"""Arrange children.
@@ -1383,6 +1399,7 @@ class Widget(DOMNode):
self._set_dirty(*regions)
self._content_width_cache = (None, 0)
self._content_height_cache = (None, 0)
self._rich_style_cache.clear()
self._repaint_required = True
if isinstance(self.parent, Widget) and self.styles.auto_dimensions:
self.parent.refresh(layout=True)
@@ -1460,6 +1477,9 @@ class Widget(DOMNode):
async def broker_event(self, event_name: str, event: events.Event) -> bool:
return await self.app._broker_event(event_name, event, default_namespace=self)
def _on_syles_updated(self) -> None:
self._rich_style_cache.clear()
async def _on_mouse_down(self, event: events.MouseUp) -> None:
await self.broker_event("mouse.down", event)

View File

@@ -1,12 +1,12 @@
from __future__ import annotations
from rich.console import RenderableType
from rich.style import Style
from rich.text import Text
import rich.repr
from .. import events
from ..reactive import Reactive
from ..reactive import Reactive, watch
from ..widget import Widget
@@ -55,6 +55,14 @@ class Footer(Widget):
"""If highlight key changes we need to regenerate the text."""
self._key_text = None
def on_mount(self) -> None:
watch(self.app, "focused", self._focus_changed)
def _focus_changed(self, focused: Widget | None) -> None:
self.log("FOCUS CHANGED", focused)
self._key_text = None
self.refresh()
async def on_mouse_move(self, event: events.MouseMove) -> None:
"""Store any key we are moving over."""
self.highlight_key = event.style.meta.get("key")
@@ -76,11 +84,9 @@ class Footer(Widget):
justify="left",
end="",
)
highlight_style = self.get_component_styles("footer--highlight").rich_style
highlight_key_style = self.get_component_styles(
"footer--highlight-key"
).rich_style
key_style = self.get_component_styles("footer--key").rich_style
highlight_style = self.get_component_rich_style("footer--highlight")
highlight_key_style = self.get_component_rich_style("footer--highlight-key")
key_style = self.get_component_rich_style("footer--key")
for binding in self.app.bindings.shown_keys:
key_display = (
binding.key.upper()

View File

@@ -342,15 +342,15 @@ class TreeControl(Generic[NodeDataType], Static, can_focus=True):
@property
def _guide_style(self) -> Style:
return self.get_component_styles("tree--guides").rich_style
return self.get_component_rich_style("tree--guides")
@property
def _highlight_guide_style(self) -> Style:
return self.get_component_styles("tree--guides-highlight").rich_style
return self.get_component_rich_style("tree--guides-highlight")
@property
def _cursor_guide_style(self) -> Style:
return self.get_component_styles("tree--guides-cursor").rich_style
return self.get_component_rich_style("tree--guides-cursor")
def on_mouse_move(self, event: events.MouseMove) -> None:
self.hover_node = event.style.meta.get("tree_node")