mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
added directory tree
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from textual.app import App, ComposeResult
|
from textual.app import App, ComposeResult
|
||||||
from textual.widgets import Header, Footer, Tree
|
from textual.widgets import Header, Footer, Tree, DirectoryTree
|
||||||
|
|
||||||
|
|
||||||
with open("food.json") as data_file:
|
with open("food.json") as data_file:
|
||||||
@@ -23,7 +23,7 @@ class TreeApp(App):
|
|||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header()
|
yield Header()
|
||||||
yield Footer()
|
yield Footer()
|
||||||
yield Tree("Root")
|
yield DirectoryTree("../")
|
||||||
|
|
||||||
def action_add(self) -> None:
|
def action_add(self) -> None:
|
||||||
tree = self.query_one(Tree)
|
tree = self.query_one(Tree)
|
||||||
|
|||||||
@@ -1,32 +1,55 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import lru_cache
|
from pathlib import Path
|
||||||
from os import scandir
|
from typing import ClassVar
|
||||||
import os.path
|
|
||||||
|
|
||||||
from rich.console import RenderableType
|
|
||||||
import rich.repr
|
|
||||||
|
|
||||||
|
from rich.style import Style
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
|
|
||||||
from ..message import Message
|
from ._tree import Tree, TreeNode, TOGGLE_STYLE
|
||||||
from .._types import MessageTarget
|
|
||||||
from ._tree_control import TreeControl, TreeNode
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DirEntry:
|
class DirEntry:
|
||||||
path: str
|
path: str
|
||||||
is_dir: bool
|
is_dir: bool
|
||||||
|
loaded: bool = False
|
||||||
|
|
||||||
|
|
||||||
class DirectoryTree(TreeControl[DirEntry]):
|
class DirectoryTree(Tree[DirEntry]):
|
||||||
@rich.repr.auto
|
|
||||||
class FileClick(Message, bubble=True):
|
COMPONENT_CLASSES: ClassVar[set[str]] = {
|
||||||
def __init__(self, sender: MessageTarget, path: str) -> None:
|
"tree--label",
|
||||||
self.path = path
|
"tree--guides",
|
||||||
super().__init__(sender)
|
"tree--guides-hover",
|
||||||
|
"tree--guides-selected",
|
||||||
|
"tree--cursor",
|
||||||
|
"tree--highlight",
|
||||||
|
"tree--highlight-line",
|
||||||
|
"directory-tree--folder",
|
||||||
|
"directory-tree--file",
|
||||||
|
"directory-tree--extension",
|
||||||
|
"directory-tree--hidden",
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFAULT_CSS = """
|
||||||
|
DirectoryTree > .directory-tree--folder {
|
||||||
|
text-style: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryTree > .directory-tree--file {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryTree > .directory-tree--extension {
|
||||||
|
text-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryTree > .directory-tree--hidden {
|
||||||
|
color: $text 50%;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -36,84 +59,69 @@ class DirectoryTree(TreeControl[DirEntry]):
|
|||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.path = os.path.expanduser(path.rstrip("/"))
|
self.path = path
|
||||||
label = os.path.basename(self.path)
|
super().__init__(
|
||||||
data = DirEntry(self.path, True)
|
path,
|
||||||
super().__init__(label, data, name=name, id=id, classes=classes)
|
data=DirEntry(path, True),
|
||||||
self.root.tree.guide_style = "black"
|
name=name,
|
||||||
|
id=id,
|
||||||
def render_node(self, node: TreeNode[DirEntry]) -> RenderableType:
|
classes=classes,
|
||||||
return self.render_tree_label(
|
|
||||||
node,
|
|
||||||
node.data.is_dir,
|
|
||||||
node.expanded,
|
|
||||||
node.is_cursor,
|
|
||||||
node.id == self.hover_node,
|
|
||||||
self.has_focus,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@lru_cache(maxsize=1024 * 32)
|
def render_label(self, node: TreeNode[DirEntry], base_style: Style, style: Style):
|
||||||
def render_tree_label(
|
node_label = node._label.copy()
|
||||||
self,
|
node_label.stylize(style)
|
||||||
node: TreeNode[DirEntry],
|
|
||||||
is_dir: bool,
|
if node._allow_expand:
|
||||||
expanded: bool,
|
prefix = ("📂 " if node.is_expanded else "📁 ", base_style + TOGGLE_STYLE)
|
||||||
is_cursor: bool,
|
node_label.stylize_before(
|
||||||
is_hover: bool,
|
self.get_component_rich_style("directory-tree--folder", partial=True)
|
||||||
has_focus: bool,
|
)
|
||||||
) -> RenderableType:
|
|
||||||
meta = {
|
|
||||||
"@click": f"click_label({node.id})",
|
|
||||||
"tree_node": node.id,
|
|
||||||
"cursor": node.is_cursor,
|
|
||||||
}
|
|
||||||
label = Text(node.label) if isinstance(node.label, str) else node.label
|
|
||||||
if is_hover:
|
|
||||||
label.stylize("underline")
|
|
||||||
if is_dir:
|
|
||||||
label.stylize("bold")
|
|
||||||
icon = "📂" if expanded else "📁"
|
|
||||||
else:
|
else:
|
||||||
icon = "📄"
|
prefix = (
|
||||||
label.highlight_regex(r"\..*$", "italic")
|
"📄 ",
|
||||||
|
base_style,
|
||||||
|
)
|
||||||
|
node_label.stylize_before(
|
||||||
|
self.get_component_rich_style("directory-tree--file", partial=True),
|
||||||
|
)
|
||||||
|
node_label.highlight_regex(
|
||||||
|
r"\..+$",
|
||||||
|
self.get_component_rich_style(
|
||||||
|
"directory-tree--extension", partial=True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
if label.plain.startswith("."):
|
if node_label.plain.startswith("."):
|
||||||
label.stylize("dim")
|
node_label.stylize_before(
|
||||||
|
self.get_component_rich_style("directory-tree--hidden")
|
||||||
|
)
|
||||||
|
|
||||||
if is_cursor and has_focus:
|
text = Text.assemble(prefix, node_label)
|
||||||
cursor_style = self.get_component_styles("tree--cursor").rich_style
|
return text
|
||||||
label.stylize(cursor_style)
|
|
||||||
|
|
||||||
icon_label = Text(f"{icon} ", no_wrap=True, overflow="ellipsis") + label
|
def load_directory(self, node: TreeNode[DirEntry]) -> None:
|
||||||
icon_label.apply_meta(meta)
|
assert node.data is not None
|
||||||
return icon_label
|
dir_path = Path(node.data.path)
|
||||||
|
node.data.loaded = True
|
||||||
def on_styles_updated(self) -> None:
|
directory = sorted(
|
||||||
self.render_tree_label.cache_clear()
|
list(dir_path.iterdir()),
|
||||||
|
key=lambda path: (not path.is_dir(), path.name.lower()),
|
||||||
|
)
|
||||||
|
for path in directory:
|
||||||
|
node.add(
|
||||||
|
path.name,
|
||||||
|
data=DirEntry(str(path), path.is_dir()),
|
||||||
|
allow_expand=path.is_dir(),
|
||||||
|
)
|
||||||
|
node.expand()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.call_after_refresh(self.load_directory, self.root)
|
self.load_directory(self.root)
|
||||||
|
|
||||||
async def load_directory(self, node: TreeNode[DirEntry]):
|
def on_tree_node_expanded(self, event: Tree.NodeSelected) -> None:
|
||||||
path = node.data.path
|
dir_entry = event.node.data
|
||||||
directory = sorted(
|
if dir_entry is None:
|
||||||
list(scandir(path)), key=lambda entry: (not entry.is_dir(), entry.name)
|
return
|
||||||
)
|
if dir_entry.is_dir and not dir_entry.loaded:
|
||||||
for entry in directory:
|
self.load_directory(event.node)
|
||||||
node.add(entry.name, DirEntry(entry.path, entry.is_dir()))
|
|
||||||
node.loaded = True
|
|
||||||
node.expand()
|
|
||||||
self.refresh(layout=True)
|
|
||||||
|
|
||||||
async def on_tree_control_node_selected(
|
|
||||||
self, message: TreeControl.NodeSelected[DirEntry]
|
|
||||||
) -> None:
|
|
||||||
dir_entry = message.node.data
|
|
||||||
if not dir_entry.is_dir:
|
|
||||||
await self.emit(self.FileClick(self, dir_entry.path))
|
|
||||||
else:
|
|
||||||
if not message.node.loaded:
|
|
||||||
await self.load_directory(message.node)
|
|
||||||
message.node.expand()
|
|
||||||
else:
|
|
||||||
message.node.toggle()
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ EventTreeDataType = TypeVar("EventTreeDataType")
|
|||||||
|
|
||||||
LineCacheKey: TypeAlias = tuple[int | tuple[int, ...], ...]
|
LineCacheKey: TypeAlias = tuple[int | tuple[int, ...], ...]
|
||||||
|
|
||||||
_TOGGLE_STYLE = Style.from_meta({"toggle": True})
|
TOGGLE_STYLE = Style.from_meta({"toggle": True})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -70,7 +70,7 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
self._parent = parent
|
self._parent = parent
|
||||||
self._id = id
|
self._id = id
|
||||||
self._label = label
|
self._label = label
|
||||||
self.data: TreeDataType = data if data is not None else tree._data_factory()
|
self.data = data
|
||||||
self._expanded = expanded
|
self._expanded = expanded
|
||||||
self._children: list[TreeNode] = []
|
self._children: list[TreeNode] = []
|
||||||
|
|
||||||
@@ -78,6 +78,7 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
self._selected = False
|
self._selected = False
|
||||||
self._allow_expand = allow_expand
|
self._allow_expand = allow_expand
|
||||||
self._updates: int = 0
|
self._updates: int = 0
|
||||||
|
self._line: int = -1
|
||||||
|
|
||||||
def __rich_repr__(self) -> rich.repr.Result:
|
def __rich_repr__(self) -> rich.repr.Result:
|
||||||
yield self._label.plain
|
yield self._label.plain
|
||||||
@@ -88,6 +89,10 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
self._selected = False
|
self._selected = False
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
|
|
||||||
|
def line(self) -> int:
|
||||||
|
"""Get the line number for this node, or -1 if it is not displayed."""
|
||||||
|
return self._line
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hover(self) -> bool:
|
def hover(self) -> bool:
|
||||||
return self._hover
|
return self._hover
|
||||||
@@ -101,7 +106,7 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
def selected(self) -> bool:
|
def selected(self) -> bool:
|
||||||
return self._selected
|
return self._selected
|
||||||
|
|
||||||
@hover.setter
|
@selected.setter
|
||||||
def selected(self, selected: bool) -> None:
|
def selected(self, selected: bool) -> None:
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
self._selected = selected
|
self._selected = selected
|
||||||
@@ -158,6 +163,10 @@ class TreeNode(Generic[TreeDataType]):
|
|||||||
self._parent._children and self._parent._children[-1] == self,
|
self._parent._children and self._parent._children[-1] == self,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def allow_expand(self) -> bool:
|
||||||
|
return self._allow_expand
|
||||||
|
|
||||||
def add(
|
def add(
|
||||||
self,
|
self,
|
||||||
label: TextType,
|
label: TextType,
|
||||||
@@ -281,11 +290,24 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self.node = node
|
self.node = node
|
||||||
super().__init__(sender)
|
super().__init__(sender)
|
||||||
|
|
||||||
|
class NodeExpanded(Generic[EventTreeDataType], Message, bubble=True):
|
||||||
|
def __init__(
|
||||||
|
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||||
|
) -> None:
|
||||||
|
self.node = node
|
||||||
|
super().__init__(sender)
|
||||||
|
|
||||||
|
class NodeCollapsed(Generic[EventTreeDataType], Message, bubble=True):
|
||||||
|
def __init__(
|
||||||
|
self, sender: MessageTarget, node: TreeNode[EventTreeDataType]
|
||||||
|
) -> None:
|
||||||
|
self.node = node
|
||||||
|
super().__init__(sender)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
label: TextType,
|
label: TextType,
|
||||||
data: TreeDataType | None = None,
|
data: TreeDataType | None = None,
|
||||||
data_factory: Callable[[], TreeDataType] = dict,
|
|
||||||
*,
|
*,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
@@ -295,7 +317,6 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
|
|
||||||
text_label = self.process_label(label)
|
text_label = self.process_label(label)
|
||||||
|
|
||||||
self._data_factory = data_factory
|
|
||||||
self._updates = 0
|
self._updates = 0
|
||||||
self._nodes: dict[NodeID, TreeNode[TreeDataType]] = {}
|
self._nodes: dict[NodeID, TreeNode[TreeDataType]] = {}
|
||||||
self._current_id = 0
|
self._current_id = 0
|
||||||
@@ -303,6 +324,21 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
|
|
||||||
self._line_cache: LRUCache[LineCacheKey, list[Segment]] = LRUCache(1024)
|
self._line_cache: LRUCache[LineCacheKey, list[Segment]] = LRUCache(1024)
|
||||||
self._tree_lines_cached: list[_TreeLine] | None = None
|
self._tree_lines_cached: list[_TreeLine] | None = None
|
||||||
|
self.cursor_node: TreeNode[TreeDataType] | None = None
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def cursor_node(self) -> TreeNode[TreeDataType] | None:
|
||||||
|
# """The node under the cursor, which may be None if no cursor is visible.
|
||||||
|
|
||||||
|
# Returns:
|
||||||
|
# TreeNode[TreeDataType] | None: A Tree node or None.
|
||||||
|
# """
|
||||||
|
# if self.cursor_line == -1:
|
||||||
|
# return None
|
||||||
|
# try:
|
||||||
|
# self._tree_lines[self.cursor_line].path[-1]
|
||||||
|
# except IndexError:
|
||||||
|
# return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process_label(cls, label: TextType):
|
def process_label(cls, label: TextType):
|
||||||
@@ -328,8 +364,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
data: TreeDataType | None,
|
data: TreeDataType | None,
|
||||||
expand: bool = False,
|
expand: bool = False,
|
||||||
) -> TreeNode[TreeDataType]:
|
) -> TreeNode[TreeDataType]:
|
||||||
node_data = data if data is not None else self._data_factory()
|
node = TreeNode(self, parent, self._new_id(), label, data, expanded=expand)
|
||||||
node = TreeNode(self, parent, self._new_id(), label, node_data, expanded=expand)
|
|
||||||
self._nodes[node._id] = node
|
self._nodes[node._id] = node
|
||||||
self._updates += 1
|
self._updates += 1
|
||||||
return node
|
return node
|
||||||
@@ -351,7 +386,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
if node._allow_expand:
|
if node._allow_expand:
|
||||||
prefix = (
|
prefix = (
|
||||||
"▼ " if node.is_expanded else "▶ ",
|
"▼ " if node.is_expanded else "▶ ",
|
||||||
base_style + _TOGGLE_STYLE,
|
base_style + TOGGLE_STYLE,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
prefix = ("", base_style)
|
prefix = ("", base_style)
|
||||||
@@ -376,6 +411,14 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self._updates += 1
|
self._updates += 1
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
|
def select_node(self, node: TreeNode | None) -> None:
|
||||||
|
"""Move the cursor to the given node, or reset cursor.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node (TreeNode | None): A tree node, or None to reset cursor.
|
||||||
|
"""
|
||||||
|
self.cursor_line = -1 if node is None else node._line
|
||||||
|
|
||||||
def get_node_at_line(self, line_no: int) -> TreeNode[TreeDataType] | None:
|
def get_node_at_line(self, line_no: int) -> TreeNode[TreeDataType] | None:
|
||||||
"""Get the node for a given line.
|
"""Get the node for a given line.
|
||||||
|
|
||||||
@@ -477,12 +520,13 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
if previous_node is not None:
|
if previous_node is not None:
|
||||||
self._refresh_node(previous_node)
|
self._refresh_node(previous_node)
|
||||||
previous_node.selected = False
|
previous_node.selected = False
|
||||||
|
self.cursor_node = None
|
||||||
|
|
||||||
node = self._get_node(line)
|
node = self._get_node(line)
|
||||||
if node is not None:
|
if node is not None:
|
||||||
self._refresh_node(node)
|
self._refresh_node(node)
|
||||||
self.scroll_to_line(line)
|
|
||||||
node.selected = True
|
node.selected = True
|
||||||
|
self.cursor_node = node
|
||||||
|
|
||||||
def watch_guide_depth(self, guide_depth: int) -> None:
|
def watch_guide_depth(self, guide_depth: int) -> None:
|
||||||
self._invalidate()
|
self._invalidate()
|
||||||
@@ -499,6 +543,16 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
"""
|
"""
|
||||||
self.scroll_to_region(Region(0, line, self.size.width, 1))
|
self.scroll_to_region(Region(0, line, self.size.width, 1))
|
||||||
|
|
||||||
|
def scroll_to_node(self, node: TreeNode) -> None:
|
||||||
|
"""Scroll to the given node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node (TreeNode): Node to scroll in to view.
|
||||||
|
"""
|
||||||
|
line = node._line
|
||||||
|
if line != -1:
|
||||||
|
self.scroll_to_line(line)
|
||||||
|
|
||||||
def refresh_line(self, line: int) -> None:
|
def refresh_line(self, line: int) -> None:
|
||||||
"""Refresh (repaint) a given line in the tree.
|
"""Refresh (repaint) a given line in the tree.
|
||||||
|
|
||||||
@@ -542,6 +596,7 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
|
|
||||||
def add_node(path: list[TreeNode], node: TreeNode, last: bool) -> None:
|
def add_node(path: list[TreeNode], node: TreeNode, last: bool) -> None:
|
||||||
child_path = [*path, node]
|
child_path = [*path, node]
|
||||||
|
node._line = len(lines)
|
||||||
add_line(_TreeLine(child_path, last))
|
add_line(_TreeLine(child_path, last))
|
||||||
if node._expanded:
|
if node._expanded:
|
||||||
for last, child in loop_last(node._children):
|
for last, child in loop_last(node._children):
|
||||||
@@ -561,6 +616,8 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
width = self.size.width
|
width = self.size.width
|
||||||
|
|
||||||
self.virtual_size = Size(width, len(lines))
|
self.virtual_size = Size(width, len(lines))
|
||||||
|
if self.cursor_node is not None:
|
||||||
|
self.cursor_line = self.cursor_node._line
|
||||||
if self.cursor_line >= len(lines):
|
if self.cursor_line >= len(lines):
|
||||||
self.cursor_line = -1
|
self.cursor_line = -1
|
||||||
|
|
||||||
@@ -693,28 +750,42 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
self._line_cache.grow(event.size.height)
|
self._line_cache.grow(event.size.height)
|
||||||
self._invalidate()
|
self._invalidate()
|
||||||
|
|
||||||
|
def _toggle_node(self, node: TreeNode[TreeDataType]) -> None:
|
||||||
|
if node.is_expanded:
|
||||||
|
node.collapse()
|
||||||
|
self.post_message_no_wait(self.NodeCollapsed(self, node))
|
||||||
|
else:
|
||||||
|
node.expand()
|
||||||
|
self.post_message_no_wait(self.NodeExpanded(self, node))
|
||||||
|
|
||||||
async def _on_click(self, event: events.Click) -> None:
|
async def _on_click(self, event: events.Click) -> None:
|
||||||
meta = event.style.meta
|
meta = event.style.meta
|
||||||
if "line" in meta:
|
if "line" in meta:
|
||||||
cursor_line = meta["line"]
|
cursor_line = meta["line"]
|
||||||
if meta.get("toggle", False):
|
if meta.get("toggle", False):
|
||||||
node = self.get_node_at_line(cursor_line)
|
node = self.get_node_at_line(cursor_line)
|
||||||
if node is not None:
|
if node is not None and self.auto_expand:
|
||||||
node.toggle()
|
self._toggle_node(node)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if self.cursor_line == cursor_line:
|
if self.cursor_line == cursor_line:
|
||||||
await self.action("select_cursor")
|
await self.action("select_cursor")
|
||||||
else:
|
else:
|
||||||
self.cursor_line = cursor_line
|
self.cursor_line = cursor_line
|
||||||
|
|
||||||
|
def _on_styles_updated(self) -> None:
|
||||||
|
self._invalidate()
|
||||||
|
|
||||||
def action_cursor_up(self) -> None:
|
def action_cursor_up(self) -> None:
|
||||||
if self.cursor_line == -1:
|
if self.cursor_line == -1:
|
||||||
self.cursor_line = len(self._tree_lines) - 1
|
self.cursor_line = len(self._tree_lines) - 1
|
||||||
else:
|
else:
|
||||||
self.cursor_line -= 1
|
self.cursor_line -= 1
|
||||||
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_cursor_down(self) -> None:
|
def action_cursor_down(self) -> None:
|
||||||
self.cursor_line += 1
|
self.cursor_line += 1
|
||||||
|
self.scroll_to_line(self.cursor_line)
|
||||||
|
|
||||||
def action_select_cursor(self) -> None:
|
def action_select_cursor(self) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -724,5 +795,5 @@ class Tree(Generic[TreeDataType], ScrollView, can_focus=True):
|
|||||||
else:
|
else:
|
||||||
node = line.path[-1]
|
node = line.path[-1]
|
||||||
if self.auto_expand:
|
if self.auto_expand:
|
||||||
node.toggle()
|
self._toggle_node(node)
|
||||||
self.emit_no_wait(self.NodeSelected(self, line.path[-1]))
|
self.post_message_no_wait(self.NodeSelected(self, node))
|
||||||
|
|||||||
Reference in New Issue
Block a user