Skip to content

Commit 86fc98a

Browse files
committed
tree demo
1 parent 09a4390 commit 86fc98a

File tree

4 files changed

+173
-1
lines changed

4 files changed

+173
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
3030
- Added support for in-band terminal resize protocol https://github.com/Textualize/textual/pull/5217
3131
- Added TEXTUAL_THEME environment var, which should be a comma separated list of desired themes
3232
- Added `Widget.is_scrolling`
33+
- Added `Tree.add_json`
3334

3435
### Changed
3536

src/textual/demo/data.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
COUNTRIES = [
24
"Afghanistan",
35
"Albania",
@@ -325,3 +327,89 @@
325327
1989-11-17,All Dogs Go to Heaven,Animation,Don Bluth,27,G,84
326328
1989-12-20,Tango & Cash,Action,Andrei Konchalovsky,63,R,104
327329
"""
330+
331+
MOVIES_JSON = """{
332+
"decades": {
333+
"1980s": {
334+
"genres": {
335+
"action": {
336+
"franchises": {
337+
"terminator": {
338+
"name": "The Terminator",
339+
"movies": [
340+
{
341+
"title": "The Terminator",
342+
"year": 1984,
343+
"director": "James Cameron",
344+
"stars": ["Arnold Schwarzenegger", "Linda Hamilton", "Michael Biehn"],
345+
"boxOffice": 78371200,
346+
"quotes": ["I'll be back", "Come with me if you want to live"]
347+
}
348+
]
349+
},
350+
"rambo": {
351+
"name": "Rambo",
352+
"movies": [
353+
{
354+
"title": "First Blood",
355+
"year": 1982,
356+
"director": "Ted Kotcheff",
357+
"stars": ["Sylvester Stallone", "Richard Crenna", "Brian Dennehy"],
358+
"boxOffice": 47212904
359+
},
360+
{
361+
"title": "Rambo: First Blood Part II",
362+
"year": 1985,
363+
"director": "George P. Cosmatos",
364+
"stars": ["Sylvester Stallone", "Richard Crenna", "Charles Napier"],
365+
"boxOffice": 150415432
366+
}
367+
]
368+
}
369+
},
370+
"standalone_classics": {
371+
"die_hard": {
372+
"title": "Die Hard",
373+
"year": 1988,
374+
"director": "John McTiernan",
375+
"stars": ["Bruce Willis", "Alan Rickman", "Reginald VelJohnson"],
376+
"boxOffice": 140700000,
377+
"location": "Nakatomi Plaza",
378+
"quotes": ["Yippee-ki-yay, motherf***er"]
379+
},
380+
"predator": {
381+
"title": "Predator",
382+
"year": 1987,
383+
"director": "John McTiernan",
384+
"stars": ["Arnold Schwarzenegger", "Carl Weathers", "Jesse Ventura"],
385+
"boxOffice": 98267558,
386+
"location": "Val Verde jungle",
387+
"quotes": ["Get to the chopper!"]
388+
}
389+
},
390+
"common_themes": [
391+
"Cold War politics",
392+
"One man army",
393+
"Revenge plots",
394+
"Military operations",
395+
"Law enforcement"
396+
],
397+
"typical_elements": {
398+
"weapons": ["M60 machine gun", "Desert Eagle", "Explosive arrows"],
399+
"vehicles": ["Military helicopters", "Muscle cars", "Tanks"],
400+
"locations": ["Urban jungle", "Actual jungle", "Industrial facilities"]
401+
}
402+
}
403+
}
404+
}
405+
},
406+
"metadata": {
407+
"total_movies": 4,
408+
"date_compiled": "2024",
409+
"box_office_total": 467654094,
410+
"most_frequent_actor": "Arnold Schwarzenegger",
411+
"most_frequent_director": "John McTiernan"
412+
}
413+
}"""
414+
415+
MOVIES_TREE = json.loads(MOVIES_JSON)

src/textual/demo/widgets.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from textual import containers, events, lazy, on
1212
from textual.app import ComposeResult
1313
from textual.binding import Binding
14-
from textual.demo.data import COUNTRIES, MOVIES
14+
from textual.demo.data import COUNTRIES, MOVIES, MOVIES_TREE
1515
from textual.demo.page import PageScreen
1616
from textual.reactive import reactive, var
1717
from textual.suggester import SuggestFromList
@@ -38,6 +38,7 @@
3838
Switch,
3939
TabbedContent,
4040
TextArea,
41+
Tree,
4142
)
4243

4344
WIDGETS_MD = """\
@@ -635,6 +636,41 @@ def switch_theme() -> None:
635636
self.set_timer(0.3, switch_theme)
636637

637638

639+
class Trees(containers.VerticalGroup):
640+
DEFAULT_CLASSES = "column"
641+
TREES_MD = """\
642+
## Tree
643+
644+
The Tree widget displays hierarchical data.
645+
646+
There is also the Tree widget's cousin, DirectoryTree, to navigate folders and files on the filesystem.
647+
"""
648+
DEFAULT_CSS = """
649+
Trees {
650+
Tree {
651+
height: 16;
652+
&.-maximized { height: 1fr; }
653+
}
654+
VerticalGroup {
655+
border: heavy transparent;
656+
&:focus-within { border: heavy $border; }
657+
}
658+
}
659+
660+
"""
661+
662+
def compose(self) -> ComposeResult:
663+
yield Markdown(self.TREES_MD)
664+
with containers.VerticalGroup():
665+
yield Tree("80s movies")
666+
667+
def on_mount(self) -> None:
668+
tree = self.query_one(Tree)
669+
tree.show_root = False
670+
tree.add_json(MOVIES_TREE)
671+
tree.root.expand()
672+
673+
638674
class TextAreas(containers.VerticalGroup):
639675
ALLOW_MAXIMIZE = True
640676
DEFAULT_CLASSES = "column"
@@ -743,5 +779,6 @@ def compose(self) -> ComposeResult:
743779
yield Sparklines()
744780
yield Switches()
745781
yield TextAreas()
782+
yield Trees()
746783
yield YourWidgets()
747784
yield Footer()

src/textual/widgets/_tree.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,52 @@ def __init__(
785785

786786
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
787787

788+
def add_json(self, json_data: object, node: TreeNode | None = None) -> None:
789+
"""Adds JSON data to a node.
790+
791+
Args:
792+
json_data: An object decoded from JSON.
793+
node: Node to add data to.
794+
795+
"""
796+
797+
if node is None:
798+
node = self.root
799+
800+
from rich.highlighter import ReprHighlighter
801+
802+
highlighter = ReprHighlighter()
803+
804+
def add_node(name: str, node: TreeNode, data: object) -> None:
805+
"""Adds a node to the tree.
806+
807+
Args:
808+
name: Name of the node.
809+
node: Parent node.
810+
data: Data associated with the node.
811+
"""
812+
if isinstance(data, dict):
813+
node.set_label(Text(f"{{}} {name}"))
814+
for key, value in data.items():
815+
new_node = node.add("")
816+
add_node(key, new_node, value)
817+
elif isinstance(data, list):
818+
node.set_label(Text(f"[] {name}"))
819+
for index, value in enumerate(data):
820+
new_node = node.add("")
821+
add_node(str(index), new_node, value)
822+
else:
823+
node.allow_expand = False
824+
if name:
825+
label = Text.assemble(
826+
Text.from_markup(f"[b]{name}[/b]="), highlighter(repr(data))
827+
)
828+
else:
829+
label = Text(repr(data))
830+
node.set_label(label)
831+
832+
add_node("", node, json_data)
833+
788834
@property
789835
def cursor_node(self) -> TreeNode[TreeDataType] | None:
790836
"""The currently selected node, or ``None`` if no selection."""

0 commit comments

Comments
 (0)