agg_gui/widgets/tree_view/node.rs
1//! Data types and flat-row engine for `TreeView`.
2
3use crate::geometry::Point;
4
5// ---------------------------------------------------------------------------
6// Public data types
7// ---------------------------------------------------------------------------
8
9/// A node in the tree.
10pub struct TreeNode {
11 pub label: String,
12 pub icon: NodeIcon,
13 /// Index of the parent node; `None` means root-level.
14 pub parent: Option<usize>,
15 pub is_expanded: bool,
16 pub is_selected: bool,
17 /// Sibling ordering key. Lower values appear first (visually higher).
18 pub order: u32,
19}
20
21impl TreeNode {
22 pub fn new(
23 label: impl Into<String>,
24 icon: NodeIcon,
25 parent: Option<usize>,
26 order: u32,
27 ) -> Self {
28 Self {
29 label: label.into(),
30 icon,
31 parent,
32 is_expanded: false,
33 is_selected: false,
34 order,
35 }
36 }
37}
38
39/// Procedurally-drawn icon discriminant.
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub enum NodeIcon {
42 Folder,
43 File,
44 Package,
45}
46
47// ---------------------------------------------------------------------------
48// Flat-row representation (recomputed every frame)
49// ---------------------------------------------------------------------------
50
51/// One visible row after DFS expansion of the tree.
52pub struct FlatRow {
53 /// Index into `TreeView::nodes`.
54 pub node_idx: usize,
55 pub depth: u32,
56 pub has_children: bool,
57}
58
59/// Produce an ordered list of visible rows by DFS traversal, respecting
60/// `is_expanded`. Nodes at each level are sorted by `order`.
61pub fn flatten_visible(nodes: &[TreeNode]) -> Vec<FlatRow> {
62 if nodes.is_empty() {
63 return Vec::new();
64 }
65
66 // Root-level nodes sorted by order.
67 let mut roots: Vec<usize> = nodes
68 .iter()
69 .enumerate()
70 .filter(|(_, n)| n.parent.is_none())
71 .map(|(i, _)| i)
72 .collect();
73 roots.sort_by_key(|&i| nodes[i].order);
74
75 let mut result = Vec::new();
76 // Stack of (node_idx, depth); push in reverse so pop gives DFS order.
77 let mut stack: Vec<(usize, u32)> =
78 roots.into_iter().rev().map(|i| (i, 0)).collect();
79
80 while let Some((node_idx, depth)) = stack.pop() {
81 let mut children: Vec<usize> = nodes
82 .iter()
83 .enumerate()
84 .filter(|(_, n)| n.parent == Some(node_idx))
85 .map(|(i, _)| i)
86 .collect();
87 children.sort_by_key(|&i| nodes[i].order);
88
89 let has_children = !children.is_empty();
90 result.push(FlatRow { node_idx, depth, has_children });
91
92 if nodes[node_idx].is_expanded && has_children {
93 for &child in children.iter().rev() {
94 stack.push((child, depth + 1));
95 }
96 }
97 }
98
99 result
100}
101
102/// Returns `true` if `node_idx` is a descendant of `ancestor_idx`.
103pub fn is_descendant(nodes: &[TreeNode], ancestor_idx: usize, mut node_idx: usize) -> bool {
104 loop {
105 match nodes[node_idx].parent {
106 None => return false,
107 Some(p) if p == ancestor_idx => return true,
108 Some(p) => node_idx = p,
109 }
110 }
111}
112
113// ---------------------------------------------------------------------------
114// Interaction state
115// ---------------------------------------------------------------------------
116
117/// Drop position relative to a reference node (by node index).
118#[derive(Clone, Copy, Debug, PartialEq)]
119pub enum DropPosition {
120 Before(usize), // insert before this node_idx in the parent's child list
121 After(usize), // insert after this node_idx in the parent's child list
122 AsChild(usize), // make a child of this node_idx
123}
124
125/// Active drag-and-drop gesture.
126pub struct DragState {
127 /// Node being dragged.
128 pub node_idx: usize,
129 /// Where in the row the cursor was when the drag started (offset from row bottom).
130 pub _cursor_row_offset: f64,
131 /// Current cursor position in TreeView local coordinates.
132 pub current_pos: Point,
133 /// `true` once the drag threshold (4 px) has been exceeded.
134 pub live: bool,
135}