Skip to main content

repose_tree/
node.rs

1//! Tree node definitions.
2
3use repose_core::{Modifier, Rect, ViewId, ViewKind};
4use slotmap::new_key_type;
5use smallvec::SmallVec;
6
7new_key_type! {
8 /// Internal node identifier, stable within a tree's lifetime.
9 pub struct NodeId;
10}
11
12/// A node in the persistent view tree.
13#[derive(Clone)]
14pub struct TreeNode {
15    /// Internal slotmap key.
16    pub id: NodeId,
17
18    /// User-facing stable view ID (for hit testing, focus, etc.).
19    pub view_id: ViewId,
20
21    /// The kind of view this node represents.
22    pub kind: ViewKind,
23
24    /// Layout and styling modifiers.
25    pub modifier: Modifier,
26
27    /// Child node IDs.
28    pub children: SmallVec<[NodeId; 4]>,
29
30    /// Parent node ID (None for root).
31    pub parent: Option<NodeId>,
32
33    /// Hash of this node's content (kind + modifier + children structure).
34    /// Used for change detection.
35    pub content_hash: u64,
36
37    /// Hash including all descendants.
38    /// If this matches, the entire subtree is unchanged.
39    pub subtree_hash: u64,
40
41    /// Cached layout result.
42    pub layout_cache: Option<LayoutCache>,
43
44    /// Generation when this node was last updated.
45    pub generation: u64,
46
47    /// Key provided by user for stable identity in dynamic lists.
48    pub user_key: Option<u64>,
49
50    /// Depth in tree (root = 0).
51    pub depth: u32,
52
53    /// If this node is a scope boundary (set by `scope!` macro), this holds the
54    /// scope key (e.g., "title", "color_buttons"). `None` for non-scope nodes.
55    pub scope_key: Option<String>,
56}
57
58impl TreeNode {
59    /// Create a new tree node.
60    pub fn new(
61        id: NodeId,
62        view_id: ViewId,
63        kind: ViewKind,
64        modifier: Modifier,
65        generation: u64,
66    ) -> Self {
67        Self {
68            id,
69            view_id,
70            kind,
71            modifier,
72            children: SmallVec::new(),
73            parent: None,
74            content_hash: 0,
75            subtree_hash: 0,
76            layout_cache: None,
77            generation,
78            user_key: None,
79            depth: 0,
80            scope_key: None,
81        }
82    }
83
84    /// Check if this node's layout cache is still valid.
85    pub fn has_valid_layout(&self) -> bool {
86        self.layout_cache.is_some()
87    }
88
89    /// Invalidate layout cache (called when content changes).
90    pub fn invalidate_layout(&mut self) {
91        self.layout_cache = None;
92    }
93
94    /// Get cached layout if available.
95    pub fn cached_rect(&self) -> Option<Rect> {
96        self.layout_cache.as_ref().map(|c| c.rect)
97    }
98
99    pub fn set_layout(&mut self, rect: Rect) {
100        self.layout_cache = Some(LayoutCache {
101            rect,
102            // constraints and screen_rect are less critical for basic paint
103            screen_rect: Rect::default(),
104            constraints: LayoutConstraints::default(),
105            generation: self.generation,
106        });
107    }
108}
109
110/// Cached layout information for a node.
111#[derive(Clone, Debug)]
112pub struct LayoutCache {
113    /// The computed rectangle in parent coordinates.
114    pub rect: Rect,
115
116    /// The computed rectangle in screen coordinates.
117    pub screen_rect: Rect,
118
119    /// Size constraints used when computing this layout.
120    pub constraints: LayoutConstraints,
121
122    /// Generation when this cache was computed.
123    pub generation: u64,
124}
125
126/// Constraints used during layout.
127#[derive(Clone, Debug, PartialEq)]
128pub struct LayoutConstraints {
129    pub min_width: f32,
130    pub max_width: f32,
131    pub min_height: f32,
132    pub max_height: f32,
133}
134
135impl Default for LayoutConstraints {
136    fn default() -> Self {
137        Self {
138            min_width: 0.0,
139            max_width: f32::INFINITY,
140            min_height: 0.0,
141            max_height: f32::INFINITY,
142        }
143    }
144}
145
146impl LayoutConstraints {
147    pub fn fixed(width: f32, height: f32) -> Self {
148        Self {
149            min_width: width,
150            max_width: width,
151            min_height: height,
152            max_height: height,
153        }
154    }
155
156    pub fn tight(size: (f32, f32)) -> Self {
157        Self::fixed(size.0, size.1)
158    }
159}
160
161/// Statistics about tree operations.
162#[derive(Clone, Debug, Default)]
163pub struct TreeStats {
164    /// Total nodes in tree.
165    pub total_nodes: usize,
166
167    /// Nodes marked dirty this frame.
168    pub dirty_nodes: usize,
169
170    /// Nodes that were reconciled (updated).
171    pub reconciled_nodes: usize,
172
173    /// Nodes that were skipped (unchanged).
174    pub skipped_nodes: usize,
175
176    /// Nodes that were created.
177    pub created_nodes: usize,
178
179    /// Nodes that were removed.
180    pub removed_nodes: usize,
181
182    /// Layout cache hits.
183    pub layout_cache_hits: usize,
184
185    /// Layout cache misses.
186    pub layout_cache_misses: usize,
187}