mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
update footer on focus change
This commit is contained in:
53
sandbox/will/bindings.py
Normal file
53
sandbox/will/bindings.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
@@ -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] = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user