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)> = roots.into_iter().rev().map(|i| (i, 0)).collect();
78
79 while let Some((node_idx, depth)) = stack.pop() {
80 let mut children: Vec<usize> = nodes
81 .iter()
82 .enumerate()
83 .filter(|(_, n)| n.parent == Some(node_idx))
84 .map(|(i, _)| i)
85 .collect();
86 children.sort_by_key(|&i| nodes[i].order);
87
88 let has_children = !children.is_empty();
89 result.push(FlatRow {
90 node_idx,
91 depth,
92 has_children,
93 });
94
95 if nodes[node_idx].is_expanded && has_children {
96 for &child in children.iter().rev() {
97 stack.push((child, depth + 1));
98 }
99 }
100 }
101
102 result
103}
104
105/// Returns `true` if `node_idx` is a descendant of `ancestor_idx`.
106pub fn is_descendant(nodes: &[TreeNode], ancestor_idx: usize, mut node_idx: usize) -> bool {
107 loop {
108 match nodes[node_idx].parent {
109 None => return false,
110 Some(p) if p == ancestor_idx => return true,
111 Some(p) => node_idx = p,
112 }
113 }
114}
115
116// ---------------------------------------------------------------------------
117// Interaction state
118// ---------------------------------------------------------------------------
119
120/// Drop position relative to a reference node (by node index).
121#[derive(Clone, Copy, Debug, PartialEq)]
122pub enum DropPosition {
123 Before(usize), // insert before this node_idx in the parent's child list
124 After(usize), // insert after this node_idx in the parent's child list
125 AsChild(usize), // make a child of this node_idx
126}
127
128/// Active drag-and-drop gesture.
129pub struct DragState {
130 /// Node being dragged.
131 pub node_idx: usize,
132 /// Where in the row the cursor was when the drag started (offset from row bottom).
133 pub _cursor_row_offset: f64,
134 /// Current cursor position in TreeView local coordinates.
135 pub current_pos: Point,
136 /// `true` once the drag threshold (4 px) has been exceeded.
137 pub live: bool,
138}