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)> =
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}