mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
fixes for state updates
This commit is contained in:
@@ -30,7 +30,7 @@ App > Screen {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 20;
|
height: 20;
|
||||||
margin: 1 2;
|
margin: 1 2;
|
||||||
background: $panel;
|
background: $surface;
|
||||||
padding: 1 2;
|
padding: 1 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -899,11 +899,12 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
self.log.system(f"{self.screen} is active")
|
self.log.system(f"{self.screen} is active")
|
||||||
return previous_screen
|
return previous_screen
|
||||||
|
|
||||||
def set_focus(self, widget: Widget | None) -> None:
|
def set_focus(self, widget: Widget | None, scroll_visible: bool = False) -> None:
|
||||||
"""Focus (or unfocus) a widget. A focused widget will receive key events first.
|
"""Focus (or unfocus) a widget. A focused widget will receive key events first.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
widget (Widget): [description]
|
widget (Widget): Widget to focus.
|
||||||
|
scroll_visible (bool, optional): Scroll widget in to view.
|
||||||
"""
|
"""
|
||||||
if widget == self.focused:
|
if widget == self.focused:
|
||||||
# Widget is already focused
|
# Widget is already focused
|
||||||
@@ -924,7 +925,8 @@ class App(Generic[ReturnType], DOMNode):
|
|||||||
# Change focus
|
# Change focus
|
||||||
self.focused = widget
|
self.focused = widget
|
||||||
# Send focus event
|
# Send focus event
|
||||||
self.screen.scroll_to_widget(widget)
|
if scroll_visible:
|
||||||
|
self.screen.scroll_to_widget(widget)
|
||||||
widget.post_message_no_wait(events.Focus(self))
|
widget.post_message_no_wait(events.Focus(self))
|
||||||
widget.emit_no_wait(events.DescendantFocus(self))
|
widget.emit_no_wait(events.DescendantFocus(self))
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ from asyncio import Lock
|
|||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from types import GeneratorType
|
|
||||||
from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple
|
from typing import TYPE_CHECKING, ClassVar, Collection, Iterable, NamedTuple
|
||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ from rich.text import Text
|
|||||||
|
|
||||||
from .. import events
|
from .. import events
|
||||||
from ..message import Message
|
from ..message import Message
|
||||||
from ..reactive import Reactive
|
|
||||||
from .._types import MessageTarget
|
from .._types import MessageTarget
|
||||||
from ._tree_control import TreeControl, TreeClick, TreeNode, NodeID
|
from ._tree_control import TreeControl, TreeNode
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -91,6 +90,9 @@ class DirectoryTree(TreeControl[DirEntry]):
|
|||||||
icon_label.apply_meta(meta)
|
icon_label.apply_meta(meta)
|
||||||
return icon_label
|
return icon_label
|
||||||
|
|
||||||
|
def on_styles_updated(self) -> None:
|
||||||
|
self.render_tree_label.cache_clear()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.call_later(self.load_directory, self.root)
|
self.call_later(self.load_directory, self.root)
|
||||||
|
|
||||||
@@ -105,7 +107,9 @@ class DirectoryTree(TreeControl[DirEntry]):
|
|||||||
node.expand()
|
node.expand()
|
||||||
self.refresh(layout=True)
|
self.refresh(layout=True)
|
||||||
|
|
||||||
async def on_tree_control_node_selected(self, message: TreeClick[DirEntry]) -> None:
|
async def on_tree_control_node_selected(
|
||||||
|
self, message: TreeControl.NodeSelected[DirEntry]
|
||||||
|
) -> None:
|
||||||
dir_entry = message.node.data
|
dir_entry = message.node.data
|
||||||
if not dir_entry.is_dir:
|
if not dir_entry.is_dir:
|
||||||
await self.emit(FileClick(self, dir_entry.path))
|
await self.emit(FileClick(self, dir_entry.path))
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from typing import ClassVar, Generic, Iterator, NewType, TypeVar
|
|||||||
|
|
||||||
import rich.repr
|
import rich.repr
|
||||||
from rich.console import RenderableType
|
from rich.console import RenderableType
|
||||||
|
from rich.style import Style
|
||||||
from rich.text import Text, TextType
|
from rich.text import Text, TextType
|
||||||
from rich.tree import Tree
|
from rich.tree import Tree
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ NodeID = NewType("NodeID", int)
|
|||||||
|
|
||||||
|
|
||||||
NodeDataType = TypeVar("NodeDataType")
|
NodeDataType = TypeVar("NodeDataType")
|
||||||
|
EventNodeDataType = TypeVar("EventNodeDataType")
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
@rich.repr.auto
|
||||||
@@ -159,21 +161,11 @@ class TreeNode(Generic[NodeDataType]):
|
|||||||
return self._control.render_node(self)
|
return self._control.render_node(self)
|
||||||
|
|
||||||
|
|
||||||
@rich.repr.auto
|
|
||||||
class TreeClick(Generic[NodeDataType], Message, bubble=True):
|
|
||||||
def __init__(self, sender: MessageTarget, node: TreeNode[NodeDataType]) -> None:
|
|
||||||
self.node = node
|
|
||||||
super().__init__(sender)
|
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
|
||||||
yield "node", self.node
|
|
||||||
|
|
||||||
|
|
||||||
class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
||||||
DEFAULT_CSS = """
|
DEFAULT_CSS = """
|
||||||
TreeControl {
|
TreeControl {
|
||||||
background: $panel;
|
background: $surface;
|
||||||
color: $text-panel;
|
color: $text-surface;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -183,7 +175,13 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
|||||||
}
|
}
|
||||||
|
|
||||||
TreeControl > .tree--guides-highlight {
|
TreeControl > .tree--guides-highlight {
|
||||||
color: $secondary;
|
color: $success;
|
||||||
|
text-style: uu;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeControl > .tree--guides-cursor {
|
||||||
|
color: $secondary;
|
||||||
text-style: bold;
|
text-style: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,12 +199,15 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
|||||||
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||||
"tree--guides",
|
"tree--guides",
|
||||||
"tree--guides-highlight",
|
"tree--guides-highlight",
|
||||||
|
"tree--guides-cursor",
|
||||||
"tree--labels",
|
"tree--labels",
|
||||||
"tree--cursor",
|
"tree--cursor",
|
||||||
}
|
}
|
||||||
|
|
||||||
class NodeSelected(Message, bubble=False):
|
class NodeSelected(Generic[EventNodeDataType], Message, bubble=False):
|
||||||
def __init__(self, sender: MessageTarget, node: TreeNode[NodeDataType]) -> None:
|
def __init__(
|
||||||
|
self, sender: MessageTarget, node: TreeNode[EventNodeDataType]
|
||||||
|
) -> None:
|
||||||
self.node = node
|
self.node = node
|
||||||
super().__init__(sender)
|
super().__init__(sender)
|
||||||
|
|
||||||
@@ -238,33 +239,10 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
|||||||
cursor_line: Reactive[int] = Reactive(0)
|
cursor_line: Reactive[int] = Reactive(0)
|
||||||
show_cursor: Reactive[bool] = Reactive(False)
|
show_cursor: Reactive[bool] = Reactive(False)
|
||||||
|
|
||||||
def watch_show_cursor(self, value: bool) -> None:
|
|
||||||
line_region = Region(0, self.cursor_line, self.size.width, 1)
|
|
||||||
self.emit_no_wait(messages.ScrollToRegion(self, line_region))
|
|
||||||
|
|
||||||
def watch_cursor_line(self, value: int) -> None:
|
def watch_cursor_line(self, value: int) -> None:
|
||||||
line_region = Region(0, value, self.size.width, 1)
|
line_region = Region(0, value, self.size.width, 1)
|
||||||
self.emit_no_wait(messages.ScrollToRegion(self, line_region))
|
self.emit_no_wait(messages.ScrollToRegion(self, line_region))
|
||||||
|
|
||||||
def watch_hover_node(self, previous_hover_node: NodeID, hover_node: NodeID) -> None:
|
|
||||||
previous_hover = self.nodes.get(previous_hover_node)
|
|
||||||
if previous_hover is not None:
|
|
||||||
previous_hover._tree.guide_style = self._guide_style
|
|
||||||
hover = self.nodes.get(hover_node)
|
|
||||||
if hover is not None:
|
|
||||||
hover._tree.guide_style = self._highlight_guide_style
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def watch_cursor(self, previous_cursor_node: NodeID, cursor_node: NodeID) -> None:
|
|
||||||
|
|
||||||
previous_cursor = self.nodes.get(previous_cursor_node)
|
|
||||||
if previous_cursor is not None:
|
|
||||||
previous_cursor._tree.guide_style = self._guide_style
|
|
||||||
cursor = self.nodes.get(cursor_node)
|
|
||||||
if cursor is not None:
|
|
||||||
cursor._tree.guide_style = self._highlight_guide_style
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
|
def get_content_height(self, container: Size, viewport: Size, width: int) -> int:
|
||||||
def get_size(tree: Tree) -> int:
|
def get_size(tree: Tree) -> int:
|
||||||
return 1 + sum(
|
return 1 + sum(
|
||||||
@@ -321,6 +299,23 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def render(self) -> RenderableType:
|
def render(self) -> RenderableType:
|
||||||
|
guide_style = self._guide_style
|
||||||
|
|
||||||
|
def update_guide_style(tree: Tree) -> None:
|
||||||
|
tree.guide_style = guide_style
|
||||||
|
for child in tree.children:
|
||||||
|
if child.expanded:
|
||||||
|
update_guide_style(child)
|
||||||
|
|
||||||
|
update_guide_style(self._tree)
|
||||||
|
if self.hover_node is not None:
|
||||||
|
hover = self.nodes.get(self.hover_node)
|
||||||
|
if hover is not None:
|
||||||
|
hover._tree.guide_style = self._highlight_guide_style
|
||||||
|
if self.cursor is not None and self.show_cursor:
|
||||||
|
cursor = self.nodes.get(self.cursor)
|
||||||
|
if cursor is not None:
|
||||||
|
cursor._tree.guide_style = self._cursor_guide_style
|
||||||
return self._tree
|
return self._tree
|
||||||
|
|
||||||
def render_node(self, node: TreeNode[NodeDataType]) -> RenderableType:
|
def render_node(self, node: TreeNode[NodeDataType]) -> RenderableType:
|
||||||
@@ -343,12 +338,33 @@ class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
|
|||||||
self.post_message_no_wait(self.NodeSelected(self, node))
|
self.post_message_no_wait(self.NodeSelected(self, node))
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self._guide_style = self.get_component_styles("tree--guides").rich_style
|
|
||||||
self._highlight_guide_style = self.get_component_styles(
|
|
||||||
"tree--guides-highlight"
|
|
||||||
).rich_style
|
|
||||||
self._tree.guide_style = self._guide_style
|
self._tree.guide_style = self._guide_style
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _guide_style(self) -> Style:
|
||||||
|
return self.get_component_styles("tree--guides").rich_style
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _highlight_guide_style(self) -> Style:
|
||||||
|
return self.get_component_styles("tree--guides-highlight").rich_style
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _cursor_guide_style(self) -> Style:
|
||||||
|
return self.get_component_styles("tree--guides-cursor").rich_style
|
||||||
|
|
||||||
|
def on_styles_updated(self) -> None:
|
||||||
|
guide_style = self._guide_style
|
||||||
|
|
||||||
|
def update_guide_style(tree: Tree) -> None:
|
||||||
|
tree.guide_style = guide_style
|
||||||
|
for child in tree.children:
|
||||||
|
if child.expanded:
|
||||||
|
update_guide_style(child)
|
||||||
|
|
||||||
|
update_guide_style(self._tree)
|
||||||
|
self._render_cache = None
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
def on_mouse_move(self, event: events.MouseMove) -> None:
|
def on_mouse_move(self, event: events.MouseMove) -> None:
|
||||||
self.hover_node = event.style.meta.get("tree_node")
|
self.hover_node = event.style.meta.get("tree_node")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user