Repository navigation
Tree
Nested, collapsible nodes with selection, a filter box and right-click menus.
from wapyt import TreeAction, TreeConfig, TreeItem
tree = self.add_tree("nav", TreeConfig(
items=[TreeItem(id="prod", label="Production", data={"kind": "server"}, items=[
TreeItem(id="prod:web", label="web-01", icon="mdi-server", data={"kind": "host"}),
])],
filterable=True,
context_actions=[
TreeAction("connect", "Connect", "mdi-lan-connect", kinds=["host"]),
TreeAction(separator=True),
TreeAction("delete", "Delete", "mdi-delete", danger=True, requires=["deletable"]),
],
))
tree.on_activate(lambda p: connect(p["node"]))
tree.on_action(lambda p: handle(p["action"], p["id"]))TreeItem(id, label, icon, open_icon, badge, tooltip, items, data). A
tooltip always shows on hover; without one, the label shows as a
tooltip only when it is cut off. A node with
items is a branch, otherwise a leaf; branches default to folder icons.
data travels in every event payload.
-
Use deterministic ids. Expansion is keyed by id and survives
set_items(), so a reload keeps what the user opened. Persist it across sessions withget_expanded()/set_expanded(ids). -
on_activate(double-click) fires for leaves only. A lazily loaded branch needs at least one child to render as a branch.
TreeAction(id, label, icon, scope, kinds, requires, danger, separator):
-
scope:"any","branch"or"leaf". -
kinds=[...]: only on nodes whosedata["kind"]is listed. For trees whose branches are different things, a server and a database say. -
requires=[...]: only on nodes whosedata["flags"]holds every name. For per-node capabilities. A node withoutflagsshows norequiresentry. - Separators between hidden groups, or first or last, hide themselves.
- Action ids may repeat (two
deleteentries for different kinds); each is reported as its ownid. - Right-clicking a node selects it.
The menu is a ContextMenu: ↑/↓, Home/End and Enter work, Escape closes it, Shift+F10 or the Menu key opens it from the keyboard, and focus returns to where it was.
Added in #63. The tree is one Tab stop. ↑/↓ move between rows, Home and End
jump to the ends, → opens a folder (or moves into an open one), ← closes it
(or moves to the parent), Enter opens a leaf (on_activate) or toggles a
folder, Space ticks a checkbox (or selects), and Shift+F10 or the Menu key
opens the right-click menu. Screen readers hear it as a tree, with each row's
level, position, and open, selected and checked state. TreeConfig(label=)
names it; tree.focus(node_id=None) moves focus into it.
Added in #63.
tree = Tree(TreeConfig(items=items, checkboxes=True), container=cell)
tree.on_check(lambda p: save_selection(p["checked_ids"]))- Click a box, or press Space on a row. Clicking a box doesn't select the row.
- By default ticking a folder ticks everything inside it, and a folder shows
ticked, unticked or mixed from what is inside.
check_cascade=Falsemakes every node independent. -
TreeItem(checked=True)starts ticked;TreeItem(checkbox=False)has no box. -
on_check→{id, checked, checked_ids}.get_checked(leaves_only=False)lists ticked nodes in tree order (fully ticked folders too, unlessleaves_only).check(id, checked=True),uncheck(id),set_checked(ids)andis_checked(id)don't fire events. Ticks surviveset_itemsfor nodes that still exist.
Added in #63.
tree = Tree(TreeConfig(items=items, draggable=True), container=cell)
async def moved(p):
if not await bff.move_node(p["id"], p["to_parent"], p["to_index"]):
tree.move(p["id"], p["from_parent"], p["from_index"]) # put it back
tree.on_move(lambda p: asyncio.ensure_future(moved(p)))- Drop on the top or bottom of a row to put the node before or after it; on the middle of a folder to put it inside. Just below an open folder means "first inside it". A closed folder opens if you hover over it, the list scrolls near its edges, and Escape cancels. A node can't go into itself.
- Keyboard: Alt+↑/↓ move the focused node among its siblings, Alt+→ puts it inside the node above, Alt+← moves it out of its folder. Screen readers hear where it went.
-
TreeItem(draggable=False)pins a node;TreeItem(droppable=False)keeps other nodes out of it;TreeConfig(drop_into_leaves=True)lets a node be dropped onto a leaf, which then becomes a folder. - The tree moves the node, then
on_movereports{id, node, from_parent, from_index, to_parent, to_index}(parents areNoneat the top level).move(id, parent, index)moves a node without an event, which is how to undo a refused move.get_items()returns the current nodes for saving. - Dragging is paused while the filter has text.
| Method | Payload |
|---|---|
on_select |
{id, node}; both are None when the selection clears |
on_activate |
{id, node}, leaves only |
on_action |
{action, id, node} |
on_toggle |
{id, expanded} |
on_filter |
{value} |
on_check |
{id, checked, checked_ids} |
on_move |
{id, node, from_parent, from_index, to_parent, to_index} |
set_items, get_node, get_parent_id, select, get_selected, expand,
collapse, toggle, expand_all, collapse_all, get_expanded,
set_expanded, set_filter, set_empty_text, get_items, move,
check, uncheck, set_checked, get_checked, is_checked, focus,
destroy.
TreeConfig also takes selected, expand_all, filter_placeholder,
empty_text, indent, label, checkboxes, check_cascade, draggable,
drop_into_leaves and extra.
wapyt is MIT-licensed · Repository · pyTincture · Issues
Getting started
Widgets
- Layout and MainWindow
- Tree
- DataTable
- Form
- ModalWindow
- Window
- TabWidget
- Toolbar
- Sidebar
- Chat
- Terminal
- CardPanel
- ResourceBoard
- File Transfer
- Message
- ContextMenu
- MenuBar
- Listbox
- Kanban
- ProgressBar
- Pagination
- Popup and tooltips
- Chart
- Scheduler
- MediaPlayer
Guides
Planning
Contributing