Skip to main content

rosin_core/
tree.rs

1//! Exposes the builder used to construct a UI tree.
2
3use std::{collections::HashMap, fmt, panic::Location, sync::Arc};
4
5use bumpalo::collections::Vec as BumpVec;
6use kurbo::{RoundedRect, Size, Vec2};
7
8use crate::{
9    hasher::IdentityBuildHasher,
10    interner::{StrId, StringInterner},
11    layout::TextCacheEntry,
12    prelude::*,
13};
14
15type EventCallback<S, H> = Box<dyn Fn(&mut S, &mut EventCtx<H>)>;
16type StyleCallback<S> = Box<dyn Fn(&S, &mut Style)>;
17type MeasureCallback<S> = Box<dyn Fn(&S, &MeasureCtx) -> Size>;
18type CanvasCallback<S> = Box<dyn Fn(&S, &mut CanvasCtx)>;
19type AccessibilityCallback<S> = Arc<dyn for<'a> Fn(&S, &mut AccessibilityCtx<'a>) + Send + Sync + 'static>;
20
21pub(crate) struct Node<S: 'static, H> {
22    pub nid: Option<NodeId>,
23    pub classes: Vec<StrId>,
24    pub text: Option<UIString>,
25    pub enabled: UIParam<bool>,
26    pub offset: Option<UIParam<Vec2>>,
27    pub event_callbacks: Vec<(On, EventCallback<S, H>)>,
28    pub style_sheet: Option<Stylesheet>,
29    pub style_callback: Option<StyleCallback<S>>,
30    pub measure_callback: Option<MeasureCallback<S>>,
31    pub canvas_callback: Option<CanvasCallback<S>>,
32    pub accessibility_callback: Option<AccessibilityCallback<S>>,
33    pub parent: usize,
34    pub num_children: usize,
35    pub subtree_size: usize,
36}
37
38impl<S, H> fmt::Debug for Node<S, H> {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.debug_struct("Node")
41            .field("id", &self.nid)
42            .field("classes", &self.classes)
43            .field("text", &self.text.is_some())
44            .field("enabled", &self.enabled.get())
45            .field("offset", &self.offset.as_ref().map(|p| p.get()))
46            .field("event_callbacks", &self.event_callbacks.len())
47            .field("style_sheet", &self.style_sheet.is_some())
48            .field("style_callback", &self.style_callback.is_some())
49            .field("canvas_callback", &self.canvas_callback.is_some())
50            .field("accessibility_callback", &self.accessibility_callback.is_some())
51            .field("parent", &self.parent)
52            .field("num_children", &self.num_children)
53            .field("subtree_size", &self.subtree_size)
54            .finish()
55    }
56}
57
58impl<S, H> Node<S, H> {
59    pub fn run_callbacks(&mut self, event_type: On, state: &mut S, ctx: &mut EventCtx<H>) -> bool {
60        let mut has_callback = false;
61        for (et, callback) in self.event_callbacks.iter_mut() {
62            if *et == event_type {
63                (callback)(state, ctx);
64                has_callback = true;
65            }
66        }
67        has_callback
68    }
69
70    pub fn has_callback(&self, event_type: On) -> bool {
71        for (et, _) in self.event_callbacks.iter() {
72            if *et == event_type {
73                return true;
74            }
75        }
76        false
77    }
78}
79
80/// Used to construct a UI tree with the builder pattern.
81pub struct Ui<S: 'static, H: 'static> {
82    /// The main array of tree nodes
83    pub(crate) nodes: Vec<Node<S, H>>,
84
85    /// The indexes of nodes that have On::AnimationFrame callbacks
86    pub(crate) on_anim_nodes: Vec<usize>,
87
88    /// The indexes of nodes that have on_style callbacks
89    pub(crate) on_style_nodes: Vec<usize>,
90
91    /// Dependency maps for each node that has a style callback
92    pub(crate) on_style_deps: HashMap<usize, DependencyMap, IdentityBuildHasher>,
93
94    /// The indexes of nodes that have a dynamic enabled UIParam, and the previous value
95    pub(crate) dynamic_enabled_prev: Vec<(usize, bool)>,
96
97    /// The indexes of nodes with the `position: fixed` property
98    pub(crate) fixed_nodes: Vec<usize>,
99
100    /// A map from NodeIds to indexes in the `nodes` array
101    pub(crate) nid_map: HashMap<NodeId, usize, IdentityBuildHasher>,
102
103    /// Sorted list of NodeIds currently present in `nid_map`
104    pub(crate) nid_sorted: Vec<NodeId>,
105
106    /// The computed styles for the tree
107    pub(crate) style_cache: Vec<Style>,
108
109    /// The computed style flags for the tree
110    pub(crate) style_flags: Vec<u8>,
111
112    /// Style dirty flags for the tree
113    pub(crate) dirty_roots: Vec<usize>,
114
115    /// New variables introduced by static rules on each node
116    pub(crate) var_scope_cache: Vec<Vec<(Arc<str>, Arc<str>)>>,
117
118    /// The computed layouts for the tree. RounededRect because it affects hit-testing.
119    pub(crate) layout_cache: Vec<RoundedRect>,
120
121    /// The computed text layouts for the tree
122    pub(crate) text_cache: HashMap<usize, TextCacheEntry, IdentityBuildHasher>,
123
124    /// The max number of children that any node in the tree has
125    pub(crate) max_children: usize,
126
127    /// The parent that we're currently adding children to
128    current_parent_idx: usize,
129
130    /// The node that we're currently applying properties to
131    current_node_idx: usize,
132}
133
134impl<S: 'static, H: 'static> fmt::Debug for Ui<S, H> {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        f.debug_struct("Ui")
137            .field("nodes", &self.nodes)
138            .field("on_anim_nodes", &self.on_anim_nodes)
139            .field("on_style_nodes", &self.on_style_nodes)
140            .field("on_style_deps", &self.on_style_deps)
141            .field("dynamic_enabled_prev", &self.dynamic_enabled_prev)
142            .field("fixed_nodes", &self.fixed_nodes)
143            .field("nid_map", &self.nid_map)
144            .field("nid_sorted", &self.nid_sorted)
145            .field("style_cache", &self.style_cache)
146            .field("style_flags", &self.style_flags)
147            .field("dirty_roots", &self.dirty_roots)
148            .field("var_scope_cache", &self.var_scope_cache)
149            .field("layout_cache", &self.layout_cache)
150            .field("text_cache", &self.text_cache)
151            .field("max_children", &self.max_children)
152            .field("current_parent_idx", &self.current_parent_idx)
153            .field("current_node_idx", &self.current_node_idx)
154            .finish()
155    }
156}
157
158impl<S, H> Ui<S, H> {
159    pub(crate) fn new() -> Self {
160        Ui {
161            nodes: Vec::with_capacity(1000),
162            on_anim_nodes: Vec::new(),
163            on_style_nodes: Vec::new(),
164            on_style_deps: HashMap::with_hasher(IdentityBuildHasher),
165            dynamic_enabled_prev: Vec::new(),
166            fixed_nodes: Vec::new(),
167            nid_map: HashMap::with_hasher(IdentityBuildHasher),
168            nid_sorted: Vec::new(),
169            style_cache: Vec::new(),
170            style_flags: Vec::new(),
171            dirty_roots: Vec::new(),
172            var_scope_cache: Vec::new(),
173            layout_cache: Vec::new(),
174            text_cache: HashMap::with_hasher(IdentityBuildHasher),
175            max_children: 0,
176            current_parent_idx: usize::MAX,
177            current_node_idx: usize::MAX,
178        }
179    }
180
181    pub(crate) fn clear(&mut self) {
182        self.nodes.clear();
183        self.on_anim_nodes.clear();
184        self.on_style_nodes.clear();
185        self.on_style_deps.clear();
186        self.dynamic_enabled_prev.clear();
187        self.fixed_nodes.clear();
188        self.nid_map.clear();
189        self.nid_sorted.clear();
190        self.style_cache.clear();
191        self.style_flags.clear();
192        self.dirty_roots.clear();
193        self.var_scope_cache.clear();
194        self.layout_cache.clear();
195        self.text_cache.clear();
196        self.max_children = 0;
197        self.current_parent_idx = usize::MAX;
198        self.current_node_idx = usize::MAX;
199    }
200
201    pub(crate) fn finish(&mut self) {
202        self.nid_sorted.clear();
203        self.nid_sorted.reserve_exact(self.nid_map.len());
204        self.nid_sorted.extend(self.nid_map.keys().copied());
205        self.nid_sorted.sort_unstable();
206
207        self.on_anim_nodes.sort_unstable();
208        self.on_anim_nodes.dedup();
209
210        self.on_style_nodes.sort_unstable();
211        self.on_style_nodes.dedup();
212
213        let len = self.nodes.len();
214        self.style_cache.resize(len, Style::default());
215        self.style_flags.reserve_exact(len);
216        self.var_scope_cache.reserve_exact(len);
217
218        self.dynamic_enabled_prev.sort_unstable();
219        self.dynamic_enabled_prev.dedup();
220        for (idx, value) in &mut self.dynamic_enabled_prev {
221            *value = self.nodes[*idx].enabled.get_or(true);
222        }
223
224        self.dirty_roots.push(0);
225    }
226
227    /// Get a list of children in tree-order
228    pub(crate) fn child_indexes(&self, parent: usize, output: &mut BumpVec<usize>) {
229        output.clear();
230        let count = self.nodes[parent].num_children;
231        if count == 0 {
232            return;
233        }
234        output.reserve(count);
235
236        // Skip each child's subtree
237        let mut idx = parent + 1;
238        let end = idx + self.nodes[parent].subtree_size;
239        while idx < end {
240            output.push(idx);
241            idx += self.nodes[idx].subtree_size + 1;
242        }
243    }
244
245    /// Accepts *_DIRTY flags, and creates a dirty root if the node responds to those flags.
246    ///
247    /// Returns true if a root was created.
248    pub(crate) fn add_dirty_root_by_nid(&mut self, nid: Option<NodeId>, flag: u8) -> bool {
249        let Some(nid) = nid else {
250            return false;
251        };
252        let Some(&idx) = self.nid_map.get(&nid) else {
253            return false;
254        };
255        self.add_dirty_root_by_idx(idx, flag)
256    }
257
258    /// Accepts *_DIRTY flags, and creates a dirty root if the node responds to those flags.
259    ///
260    /// Returns true if a root was created.
261    pub(crate) fn add_dirty_root_by_idx(&mut self, idx: usize, flag: u8) -> bool {
262        if (self.style_flags[idx] & flag) != 0 {
263            self.dirty_roots.push(idx);
264            true
265        } else {
266            false
267        }
268    }
269
270    /// Sorts and merges the list of dirty roots.
271    pub(crate) fn merge_dirty_roots(&mut self) {
272        self.dirty_roots.sort_unstable();
273
274        let mut kept = 0usize; // count of roots we've kept
275        let mut dirty_until = 0usize;
276
277        for r in 0..self.dirty_roots.len() {
278            let idx = self.dirty_roots[r];
279
280            // Dedup exact duplicates
281            if kept != 0 && self.dirty_roots[kept - 1] == idx {
282                continue;
283            }
284
285            // Skip roots covered by a previously kept dirty subtree
286            if idx < dirty_until {
287                continue;
288            }
289
290            self.dirty_roots[kept] = idx;
291            kept += 1;
292
293            dirty_until = idx + self.nodes[idx].subtree_size + 1;
294        }
295
296        self.dirty_roots.truncate(kept);
297    }
298
299    /// Create a new node in the tree and make it the current node for subsequent builder calls to configure.
300    ///
301    /// **Panics**: If called a second time at the root level (only one root node is allowed).
302    #[track_caller]
303    #[inline]
304    pub fn node(&mut self) -> &mut Self {
305        debug_assert!(self.current_parent_idx != usize::MAX || self.nodes.is_empty(), "There can only be one root node.");
306
307        if self.current_parent_idx != usize::MAX {
308            let parent = &mut self.nodes[self.current_parent_idx];
309            parent.num_children += 1;
310        }
311
312        self.current_node_idx = self.nodes.len();
313
314        self.nodes.push(Node {
315            nid: None,
316            classes: Vec::new(),
317            text: None,
318            enabled: UIParam::Static(true),
319            offset: None,
320            event_callbacks: Vec::new(),
321            style_sheet: None,
322            style_callback: None,
323            measure_callback: None,
324            canvas_callback: None,
325            accessibility_callback: None,
326            parent: self.current_parent_idx,
327            num_children: 0,
328            subtree_size: 0,
329        });
330
331        self
332    }
333
334    /// Assign an id to the current node, providing a stable identity between build phases.
335    ///
336    /// **Panics**: If the ID has already been assigned to a different node.
337    #[track_caller]
338    #[inline]
339    pub fn id(&mut self, id: NodeId) -> &mut Self {
340        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
341        let current_node = &mut self.nodes[self.current_node_idx];
342
343        if let Some(old_id) = current_node.nid {
344            self.nid_map.remove(&old_id);
345        }
346
347        current_node.nid = Some(id);
348
349        if self.nid_map.insert(id, self.current_node_idx).is_some() {
350            let location = Location::caller();
351            panic!("NodeId reused at {location}.");
352        }
353
354        self
355    }
356
357    /// Append a space-separated list of CSS classes to the current node.
358    ///
359    /// You can pass in `None` to remove all classes from the current node.
360    #[track_caller]
361    #[inline]
362    pub fn classes<'a>(&mut self, classes: impl Into<Option<&'a str>>) -> &mut Self {
363        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
364        let current_node = &mut self.nodes[self.current_node_idx];
365        let classes = classes.into();
366        if let Some(classes) = classes {
367            let mut interner = StringInterner::global().write();
368            for class in classes.split_whitespace() {
369                let str_id = interner.intern(class);
370                if !current_node.classes.contains(&str_id) {
371                    current_node.classes.push(str_id);
372                }
373            }
374        } else {
375            current_node.classes.clear();
376        }
377        self
378    }
379
380    /// Sets the text to be rendered inside this node.
381    #[track_caller]
382    #[inline]
383    pub fn text(&mut self, text: impl Into<UIString>) -> &mut Self {
384        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
385        let current_node = &mut self.nodes[self.current_node_idx];
386        current_node.text = Some(text.into());
387        self
388    }
389
390    /// Sets whether the current node is enabled. Disabled nodes do not receive events and may be styled differently.
391    ///
392    /// Accepts `impl Into<UIParam>` so you can pass a reactive [`Var`] or a constant [`bool`] directly.
393    #[track_caller]
394    #[inline]
395    pub fn enabled(&mut self, enabled: impl Into<UIParam<bool>>) -> &mut Self {
396        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
397        let enabled = enabled.into();
398        if let UIParam::Dynamic(_) = enabled {
399            // we can't get the current value now because reading it would register it as a build phase dependency
400            self.dynamic_enabled_prev.push((self.current_node_idx, true));
401        }
402        self.nodes[self.current_node_idx].enabled = enabled;
403        self
404    }
405
406    /// Provide an offset value to change the position of child nodes. Useful for scroll areas.
407    ///     
408    /// Accepts `impl Into<UIParam>` so you can pass a reactive [`Var`] or a constant [`Vec2`] directly.
409    #[track_caller]
410    #[inline]
411    pub fn offset(&mut self, offset: impl Into<UIParam<Vec2>>) -> &mut Self {
412        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
413        self.nodes[self.current_node_idx].offset = Some(offset.into());
414        self
415    }
416
417    /// Apply a stylesheet to the current node and all of its children.
418    ///
419    /// You can pass in `None` to remove a stylesheet from the current node. There can only be one stylesheet per node.
420    #[track_caller]
421    #[inline]
422    pub fn style_sheet<'a>(&mut self, style_sheet: impl Into<Option<&'a Stylesheet>>) -> &mut Self {
423        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
424        self.nodes[self.current_node_idx].style_sheet = style_sheet.into().cloned();
425        self
426    }
427
428    /// Register an event callback.
429    #[track_caller]
430    #[inline]
431    pub fn event(&mut self, event_type: On, callback: impl Fn(&mut S, &mut EventCtx<H>) + 'static) -> &mut Self {
432        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
433        if event_type == On::AnimationFrame {
434            self.on_anim_nodes.push(self.current_node_idx);
435        }
436        self.nodes[self.current_node_idx].event_callbacks.push((event_type, Box::new(callback)));
437        self
438    }
439
440    /// Register a function to modify the current node's style before the layout and draw phases.
441    /// Useful for applying the results of an [`On::AnimationFrame`] event handler.
442    ///
443    /// There can only be one `on_style` callback per node.
444    #[track_caller]
445    #[inline]
446    pub fn on_style(&mut self, callback: impl Fn(&S, &mut Style) + 'static) -> &mut Self {
447        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
448        self.on_style_nodes.push(self.current_node_idx);
449        self.nodes[self.current_node_idx].style_callback = Some(Box::new(callback));
450        self
451    }
452
453    /// Register a function to calculate the node's preferred size based on the provided constraints.
454    /// This overrides the node's built-in text layout measurement.
455    ///
456    /// There can only be one `on_measure` callback per node.
457    #[track_caller]
458    #[inline]
459    pub fn on_measure(&mut self, callback: impl Fn(&S, &MeasureCtx) -> Size + 'static) -> &mut Self {
460        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
461        self.nodes[self.current_node_idx].measure_callback = Some(Box::new(callback));
462        self
463    }
464
465    /// Register a function to draw arbitrary vector graphics inside the current node.
466    ///
467    /// There can only be one `on_canvas` callback per node.
468    #[track_caller]
469    #[inline]
470    pub fn on_canvas(&mut self, callback: impl Fn(&S, &mut CanvasCtx) + 'static) -> &mut Self {
471        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
472        self.nodes[self.current_node_idx].canvas_callback = Some(Box::new(callback));
473        self
474    }
475
476    /// Register a function to construct the accessability information for the current node if the platform requests it.
477    ///
478    /// There can only be one `on_accessability` callback per node.
479    #[track_caller]
480    #[inline]
481    pub fn on_accessibility(&mut self, f: impl for<'a> Fn(&S, &mut AccessibilityCtx<'a>) + Send + Sync + 'static) -> &mut Self {
482        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
483        self.nodes[self.current_node_idx].accessibility_callback = Some(std::sync::Arc::new(f));
484        self
485    }
486
487    /// Add child nodes.
488    #[track_caller]
489    #[inline]
490    pub fn children(&mut self, func: impl FnOnce(&mut Ui<S, H>)) -> &mut Self {
491        debug_assert!(self.current_node_idx != usize::MAX, "You must call .node() before setting properties.");
492        let parent_index = self.current_parent_idx;
493        let node_index = self.current_node_idx;
494
495        self.current_parent_idx = node_index;
496        self.current_node_idx = usize::MAX;
497        func(self);
498
499        let subtree_size = self.nodes.len() - (1 + node_index);
500        self.nodes[node_index].subtree_size = subtree_size;
501
502        self.current_parent_idx = parent_index;
503        self.current_node_idx = node_index;
504        self.max_children = self.max_children.max(self.nodes[node_index].num_children);
505
506        self
507    }
508}