Skip to main content

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}