This commit is contained in:
Will McGugan
2022-08-11 13:33:26 +01:00
parent 806582fcb4
commit 1a1076bbea
4 changed files with 72 additions and 48 deletions

View File

@@ -24,6 +24,23 @@ App > Screen {
}
#tree-container {
overflow-y: auto;
height: 20;
margin: 1 2;
background: $panel;
padding: 1 2;
}
DirectoryTree {
padding: 0 1;
height: auto;
}
DataTable {
/*border:heavy red;*/
/* tint: 10% green; */

View File

@@ -6,7 +6,8 @@ from rich.text import Text
from textual.app import App, ComposeResult
from textual.reactive import Reactive
from textual.widget import Widget
from textual.widgets import Static, DataTable
from textual.widgets import Static, DataTable, DirectoryTree
from textual.layout import Vertical
CODE = '''
from __future__ import annotations
@@ -130,6 +131,7 @@ class BasicApp(App, css_path="basic.css"):
classes="scrollable",
),
table,
Widget(DirectoryTree("~/projects/textual"), id="tree-container"),
Error(),
Tweet(TweetBody(), classes="scrollbar-size-custom"),
Warning(),

View File

@@ -13,7 +13,7 @@ from .. import events
from ..message import Message
from ..reactive import Reactive
from .._types import MessageTarget
from . import TreeControl, TreeClick, TreeNode, NodeID
from ._tree_control import TreeControl, TreeClick, TreeNode, NodeID
@dataclass
@@ -30,11 +30,18 @@ class FileClick(Message, bubble=True):
class DirectoryTree(TreeControl[DirEntry]):
def __init__(self, path: str, name: str = None) -> None:
self.path = path.rstrip("/")
def __init__(
self,
path: str,
*,
name: str | None = None,
id: str | None = None,
classes: str | None = None,
) -> None:
self.path = os.path.expanduser(path.rstrip("/"))
label = os.path.basename(self.path)
data = DirEntry(path, True)
super().__init__(label, name=name, data=data)
data = DirEntry(self.path, True)
super().__init__(label, data, name=name, id=id, classes=classes)
self.root.tree.guide_style = "black"
has_focus: Reactive[bool] = Reactive(False)
@@ -50,7 +57,7 @@ class DirectoryTree(TreeControl[DirEntry]):
node.tree.guide_style = (
"bold not dim red" if node.id == hover_node else "black"
)
self.refresh(layout=True)
self.refresh()
def render_node(self, node: TreeNode[DirEntry]) -> RenderableType:
return self.render_tree_label(
@@ -81,12 +88,12 @@ class DirectoryTree(TreeControl[DirEntry]):
if is_hover:
label.stylize("underline")
if is_dir:
label.stylize("bold magenta")
label.stylize("bold")
icon = "📂" if expanded else "📁"
else:
label.stylize("bright_green")
icon = "📄"
label.highlight_regex(r"\..*$", "green")
label.highlight_regex(r"\..*$", "italic")
if label.plain.startswith("."):
label.stylize("dim")
@@ -112,7 +119,7 @@ class DirectoryTree(TreeControl[DirEntry]):
await node.expand()
self.refresh(layout=True)
async def handle_tree_click(self, message: TreeClick[DirEntry]) -> None:
async def on_tree_click(self, message: TreeClick[DirEntry]) -> None:
dir_entry = message.node.data
if not dir_entry.is_dir:
await self.emit(FileClick(self, dir_entry.path))

View File

@@ -1,15 +1,14 @@
from __future__ import annotations
from typing import Generic, Iterator, NewType, TypeVar
from typing import ClassVar, Generic, Iterator, NewType, TypeVar
import rich.repr
from rich.console import RenderableType
from rich.style import Style
from rich.text import Text, TextType
from rich.tree import Tree
from rich.padding import PaddingDimensions
from .. import events
from ..reactive import Reactive
from .._types import MessageTarget
from ..widget import Widget
@@ -169,33 +168,52 @@ class TreeClick(Generic[NodeDataType], Message, bubble=True):
yield "node", self.node
class TreeControl(Generic[NodeDataType], Widget):
class TreeControl(Generic[NodeDataType], Widget, can_focus=True):
CSS = """
TreeControl {
background: $panel;
color: $text-panel;
height: auto;
width: 100%;
}
TreeControl > .tree--guides {
color: $secondary;
}
"""
COMPONENT_CLASSES: ClassVar[set[str]] = {
"tree--guides",
"tree--labels",
}
def __init__(
self,
label: TextType,
data: NodeDataType,
*,
name: str | None = None,
padding: PaddingDimensions = (1, 1),
id: str | None = None,
classes: str | None = None,
) -> None:
self.data = data
self.id = NodeID(0)
self.node_id = NodeID(0)
self.nodes: dict[NodeID, TreeNode[NodeDataType]] = {}
self._tree = Tree(label)
self.root: TreeNode[NodeDataType] = TreeNode(
None, self.id, self, self._tree, label, data
None, self.node_id, self, self._tree, label, data
)
self._tree.label = self.root
self.nodes[NodeID(self.id)] = self.root
super().__init__(name=name)
self.padding = padding
self.nodes[NodeID(self.node_id)] = self.root
super().__init__(name=name, id=id, classes=classes)
hover_node: Reactive[NodeID | None] = Reactive(None)
cursor: Reactive[NodeID] = Reactive(NodeID(0), layout=True)
cursor_line: Reactive[int] = Reactive(0, repaint=False)
show_cursor: Reactive[bool] = Reactive(False, layout=True)
cursor: Reactive[NodeID] = Reactive(NodeID(0))
cursor_line: Reactive[int] = Reactive(0)
show_cursor: Reactive[bool] = Reactive(False)
def watch_show_cursor(self, value: bool) -> None:
self.emit_no_wait(CursorMove(self, self.cursor_line))
@@ -211,14 +229,14 @@ class TreeControl(Generic[NodeDataType], Widget):
data: NodeDataType,
) -> None:
parent = self.nodes[node_id]
self.id = NodeID(self.id + 1)
self.node_id = NodeID(self.node_id + 1)
child_tree = parent._tree.add(label)
child_node: TreeNode[NodeDataType] = TreeNode(
parent, self.id, self, child_tree, label, data
parent, self.node_id, self, child_tree, label, data
)
parent.children.append(child_node)
child_tree.label = child_node
self.nodes[self.id] = child_node
self.nodes[self.node_id] = child_node
self.refresh(layout=True)
@@ -249,6 +267,7 @@ class TreeControl(Generic[NodeDataType], Widget):
return None
def render(self) -> RenderableType:
self._tree.guide_style = self.component_styles["tree--guides"].node.rich_style
return self._tree
def render_node(self, node: TreeNode[NodeDataType]) -> RenderableType:
@@ -307,24 +326,3 @@ class TreeControl(Generic[NodeDataType], Widget):
if previous_node is not None:
self.cursor_line -= 1
self.cursor = previous_node.id
if __name__ == "__main__":
from textual import events
from textual.app import App
class TreeApp(App):
async def on_mount(self, event: events.Mount) -> None:
await self.screen.dock(TreeControl("Tree Root", data="foo"))
async def handle_tree_click(self, message: TreeClick) -> None:
if message.node.empty:
await message.node.add("foo")
await message.node.add("bar")
await message.node.add("baz")
await message.node.expand()
else:
await message.node.toggle()
TreeApp(log_path="textual.log").run()