Tree View

a collapsible hierarchy you can select from and edit.

▶ Run in browser

Tags: ui tree hierarchy signals

TreeView draws a hierarchy of TreeItems, one row per visible item, indented by depth. An item is plain data: TreeItem("World") holds a text, its children, an expanded flag (True by default) and an optional data payload for your own objects. parent.add_child(item) hangs one item under another and returns it, so a branch can be built and kept in one line. Hand the top item to TreeView(root=...) and every expanded branch below it is shown.

Clicking the arrow in front of a branch collapses or expands it and fires item_collapsed or item_expanded; clicking anywhere else on a row selects that item, highlights it and fires item_selected(item). The status line here listens to that signal, and the view’s selected attribute is the selected item (or None) whenever you need to ask.

Add Node hangs a new child off the selected item (or the root when nothing is selected) and expands it so the new row is visible; Remove Selected takes the selected item out of its parent (the root has none, so it stays). The view does not watch its items: it flattens them into rows once and reuses that list every frame, so after editing the graph in code (adding, removing, renaming, or setting expanded) call the view’s refresh() to put the change on screen.

The panel is anchored to the window with anchor=Anchor.FULL_RECT, so it follows every resize, and the VBoxContainer inside it stacks the title, the tree, the status line and the button row, stretching each row to its width (the buttons inside that row keep their own size). The column’s sizing is SizingMode.EXPAND, which shares its spare height out by each row’s stretch_ratio; every row but the tree sets that to 0, so the tree takes the height the other rows leave.

Source

  1# /// simvx
  2# tags = ["ui", "tree", "hierarchy", "signals"]
  3# web = { width = 800, height = 600, responsive = true }
  4# ///
  5"""Tree View: a collapsible hierarchy you can select from and edit.
  6
  7`TreeView` draws a hierarchy of `TreeItem`s, one row per visible item, indented
  8by depth. An item is plain data: `TreeItem("World")` holds a `text`, its
  9`children`, an `expanded` flag (True by default) and an optional `data` payload
 10for your own objects. `parent.add_child(item)` hangs one item under another and
 11returns it, so a branch can be built and kept in one line. Hand the top item to
 12`TreeView(root=...)` and every expanded branch below it is shown.
 13
 14Clicking the arrow in front of a branch collapses or expands it and fires
 15`item_collapsed` or `item_expanded`; clicking anywhere else on a row selects
 16that item, highlights it and fires `item_selected(item)`. The status line here
 17listens to that signal, and the view's `selected` attribute is the selected
 18item (or `None`) whenever you need to ask.
 19
 20Add Node hangs a new child off the selected item (or the root when nothing is
 21selected) and expands it so the new row is visible; Remove Selected takes the
 22selected item out of its parent (the root has none, so it stays). The view does
 23not watch its items: it flattens them into rows once and reuses that list every
 24frame, so after editing the graph in code (adding, removing, renaming, or
 25setting `expanded`) call the view's `refresh()` to put the change on screen.
 26
 27The panel is anchored to the window with `anchor=Anchor.FULL_RECT`, so it
 28follows every resize, and the `VBoxContainer` inside it stacks the title, the
 29tree, the status line and the button row, stretching each row to its width
 30(the buttons inside that row keep their own size). The column's `sizing` is
 31`SizingMode.EXPAND`, which shares its spare height out by each row's
 32`stretch_ratio`; every row but the tree sets that to 0, so the tree takes the
 33height the other rows leave.
 34"""
 35
 36from simvx.core import (
 37    Anchor,
 38    Button,
 39    Colour,
 40    HBoxContainer,
 41    Label,
 42    Node,
 43    Panel,
 44    SizingMode,
 45    TreeItem,
 46    TreeView,
 47    VBoxContainer,
 48)
 49from simvx.graphics import App
 50
 51
 52class TreeDemo(Node):
 53    def on_ready(self):
 54        self.added = 0
 55        root = TreeItem("Root")
 56        world = root.add_child(TreeItem("World"))
 57        for name in ("Player (Node3D)", "Enemy1 (Node3D)", "Enemy2 (Node3D)"):
 58            world.add_child(TreeItem(name))
 59        ui = root.add_child(TreeItem("UI"))
 60        ui.add_child(TreeItem("HUD"))
 61        ui.add_child(TreeItem("Menu"))
 62        root.add_child(TreeItem("Camera"))
 63
 64        panel = self.add_child(Panel(anchor=Anchor.FULL_RECT, inset=(30, 20)))
 65        panel.bg_colour = Colour.hex("#1A1A2E")
 66        column = panel.add_child(VBoxContainer(anchor=Anchor.FULL_RECT, inset=10, separation=10))
 67        column.sizing = SizingMode.EXPAND
 68
 69        title = column.add_child(Label("Scene Tree Inspector", font_size=18, alignment="centre", stretch_ratio=0))
 70        title.text_colour = Colour.hex("#E94560")
 71        self.view = column.add_child(TreeView(root=root, name="SceneTree"))
 72        self.view.select_colour = Colour.hex("#0F3460")
 73        self.view.item_selected.connect(self.show_selection)
 74        self.status = column.add_child(Label("Selected: (none)", stretch_ratio=0))
 75        self.status.text_colour = Colour.CYAN
 76
 77        buttons = [Button("Add Node", on_press=self.add_node), Button("Remove Selected", on_press=self.remove_selected)]
 78        column.add_child(HBoxContainer(children=buttons, separation=10, stretch_ratio=0))
 79
 80    def show_selection(self, item: TreeItem | None):
 81        self.status.text = f"Selected: {item.text if item else '(none)'}"
 82
 83    def add_node(self):
 84        parent = self.view.selected or self.view.root
 85        self.added += 1
 86        parent.add_child(TreeItem(f"NewNode{self.added}"))
 87        parent.expanded = True
 88        self.view.refresh()
 89
 90    def remove_selected(self):
 91        item = self.view.selected
 92        if item and item.parent:  # the root has no parent and stays
 93            item.parent.remove_child(item)
 94            self.view.selected = None
 95            self.show_selection(None)
 96            self.view.refresh()
 97
 98
 99if __name__ == "__main__":
100    App(title="Tree View", width=800, height=600).run(TreeDemo())