Skip to main content

microui_redux/widget_tree/
builder.rs

1//
2// Copyright 2022-Present (c) Raja Lehtihet & Wael El Oraiby
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are met:
6//
7// 1. Redistributions of source code must retain the above copyright notice,
8// this list of conditions and the following disclaimer.
9//
10// 2. Redistributions in binary form must reproduce the above copyright notice,
11// this list of conditions and the following disclaimer in the documentation
12// and/or other materials provided with the distribution.
13//
14// 3. Neither the name of the copyright holder nor the names of its contributors
15// may be used to endorse or promote products derived from this software without
16// specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28// POSSIBILITY OF SUCH DAMAGE.
29//
30// -----------------------------------------------------------------------------
31//! Builder APIs for assembling retained widget trees with stable IDs.
32
33use std::{
34    cell::RefCell,
35    collections::hash_map::DefaultHasher,
36    hash::{Hash, Hasher},
37    rc::Rc,
38};
39
40use rs_math3d::Dimensioni;
41
42use crate::{
43    input::{ContainerOption, WidgetBehaviourOption},
44    layout::{SizePolicy, StackDirection},
45    widget::Widget,
46    ContainerHandle, Custom, CustomRenderArgs, Node, TextBlock, TextWrap,
47};
48
49use super::{erased_widget_state, widget_handle, NodeId, Policy, TreeCustomRender, WidgetHandle, WidgetTree, WidgetTreeNode, WidgetTreeNodeKind};
50
51struct BuilderFrame {
52    scope_seed: u64,
53    next_auto: u64,
54    nodes: Vec<WidgetTreeNode>,
55}
56
57impl BuilderFrame {
58    fn root(seed: u64) -> Self {
59        Self {
60            scope_seed: seed,
61            next_auto: 0,
62            nodes: Vec::new(),
63        }
64    }
65
66    fn child(seed: u64) -> Self {
67        Self {
68            scope_seed: seed,
69            next_auto: 0,
70            nodes: Vec::new(),
71        }
72    }
73}
74
75#[derive(Copy, Clone, Debug, PartialEq)]
76/// Optional metadata applied when inserting one retained node.
77///
78/// This keeps identity and placement concerns in one place so the builder API
79/// does not need separate `keyed_*` and `*_with_policy` method families.
80pub struct NodeOptions {
81    policy: Policy,
82    key: Option<u64>,
83}
84
85impl Default for NodeOptions {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl NodeOptions {
92    /// Creates default options with automatic placement and no explicit key.
93    pub const fn new() -> Self {
94        Self { policy: Policy::auto(), key: None }
95    }
96
97    /// Creates options with an explicit placement policy.
98    pub const fn with_policy(policy: Policy) -> Self {
99        Self { policy, key: None }
100    }
101
102    /// Creates options keyed from the provided value.
103    pub fn keyed<K: Hash>(key: K) -> Self {
104        Self::new().key(key)
105    }
106
107    /// Replaces the placement policy.
108    pub const fn policy(mut self, policy: Policy) -> Self {
109        self.policy = policy;
110        self
111    }
112
113    /// Stores a stable hashed key for this node.
114    pub fn key<K: Hash>(mut self, key: K) -> Self {
115        self.key = Some(hash_builder_key(key));
116        self
117    }
118}
119
120fn hash_builder_key<K: Hash>(key: K) -> u64 {
121    let mut hasher = DefaultHasher::new();
122    key.hash(&mut hasher);
123    hasher.finish()
124}
125
126/// Builder that creates a retained widget tree.
127///
128/// Unkeyed methods derive IDs from sibling order and remain stable only while
129/// the surrounding structure stays in the same order. Use `*_with` together
130/// with [`NodeOptions::keyed`] for dynamic or reorderable children.
131pub struct WidgetTreeBuilder {
132    frames: Vec<BuilderFrame>,
133}
134
135impl Default for WidgetTreeBuilder {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl WidgetTreeBuilder {
142    /// Default root seed used by [`WidgetTreeBuilder::new`].
143    pub const DEFAULT_ROOT_SEED: u64 = 0x9e37_79b9_7f4a_7c15;
144
145    /// Creates an empty builder with a root scope.
146    pub fn new() -> Self {
147        Self::with_seed(Self::DEFAULT_ROOT_SEED)
148    }
149
150    /// Creates an empty builder whose root IDs are derived from `seed`.
151    pub fn with_seed(seed: u64) -> Self {
152        Self { frames: vec![BuilderFrame::root(seed)] }
153    }
154
155    /// Builds a retained tree by executing `f` within a fresh builder.
156    pub fn build(f: impl FnOnce(&mut Self)) -> WidgetTree {
157        let mut builder = Self::new();
158        f(&mut builder);
159        builder.finish()
160    }
161
162    /// Builds a retained tree whose root IDs are derived from `seed`.
163    pub fn build_with_seed(seed: u64, f: impl FnOnce(&mut Self)) -> WidgetTree {
164        let mut builder = Self::with_seed(seed);
165        f(&mut builder);
166        builder.finish()
167    }
168
169    /// Finishes the builder and returns the resulting tree.
170    pub fn finish(mut self) -> WidgetTree {
171        debug_assert_eq!(self.frames.len(), 1, "widget tree builder scopes must be balanced");
172        let frame = self.frames.pop().expect("root frame missing");
173        WidgetTree { roots: frame.nodes }
174    }
175
176    /// Adds an unkeyed widget leaf node.
177    pub fn widget<W: Widget + 'static>(&mut self, widget: WidgetHandle<W>) -> NodeId {
178        self.widget_with(NodeOptions::new(), widget)
179    }
180
181    /// Adds a widget leaf node with optional identity and placement metadata.
182    pub fn widget_with<W: Widget + 'static>(&mut self, options: NodeOptions, widget: WidgetHandle<W>) -> NodeId {
183        self.push_leaf(options, WidgetTreeNodeKind::Widget { widget: erased_widget_state(widget) })
184    }
185
186    /// Adds a text block without wrapping.
187    pub fn text(&mut self, text: impl Into<String>) -> NodeId {
188        let text = text.into();
189        self.widget(widget_handle(TextBlock::new(text)))
190    }
191
192    /// Adds a wrapped text block.
193    pub fn text_with_wrap(&mut self, text: impl Into<String>, wrap: TextWrap) -> NodeId {
194        let text = text.into();
195        self.widget(widget_handle(TextBlock::with_wrap(text, wrap)))
196    }
197
198    /// Adds a custom-render widget node.
199    pub fn custom_render<F>(&mut self, state: WidgetHandle<Custom>, f: F) -> NodeId
200    where
201        F: FnMut(Dimensioni, &CustomRenderArgs) + 'static,
202    {
203        self.custom_render_with(NodeOptions::new(), state, f)
204    }
205
206    /// Adds a custom-render widget node with optional identity and placement metadata.
207    pub fn custom_render_with<F>(&mut self, options: NodeOptions, state: WidgetHandle<Custom>, f: F) -> NodeId
208    where
209        F: FnMut(Dimensioni, &CustomRenderArgs) + 'static,
210    {
211        let render: TreeCustomRender = Rc::new(RefCell::new(Box::new(f)));
212        self.push_leaf(options, WidgetTreeNodeKind::CustomRender { state, render })
213    }
214
215    /// Adds an unkeyed embedded container node.
216    pub fn container(&mut self, handle: ContainerHandle, opt: ContainerOption, behaviour: WidgetBehaviourOption, f: impl FnOnce(&mut Self)) -> NodeId {
217        self.container_with(NodeOptions::new(), handle, opt, behaviour, f)
218    }
219
220    /// Adds an embedded container node with optional identity and placement metadata.
221    pub fn container_with(
222        &mut self,
223        options: NodeOptions,
224        handle: ContainerHandle,
225        opt: ContainerOption,
226        behaviour: WidgetBehaviourOption,
227        f: impl FnOnce(&mut Self),
228    ) -> NodeId {
229        self.push_group(options, WidgetTreeNodeKind::Container { handle, opt, behaviour }, f)
230    }
231
232    /// Adds an unkeyed collapsible header node.
233    pub fn header(&mut self, state: WidgetHandle<Node>, f: impl FnOnce(&mut Self)) -> NodeId {
234        self.header_with(NodeOptions::new(), state, f)
235    }
236
237    /// Adds a collapsible header node with optional identity and placement metadata.
238    pub fn header_with(&mut self, options: NodeOptions, state: WidgetHandle<Node>, f: impl FnOnce(&mut Self)) -> NodeId {
239        self.push_group(options, WidgetTreeNodeKind::Header { state }, f)
240    }
241
242    /// Adds an unkeyed tree node that indents its children while expanded.
243    pub fn tree_node(&mut self, state: WidgetHandle<Node>, f: impl FnOnce(&mut Self)) -> NodeId {
244        self.tree_node_with(NodeOptions::new(), state, f)
245    }
246
247    /// Adds a tree node with optional identity and placement metadata.
248    pub fn tree_node_with(&mut self, options: NodeOptions, state: WidgetHandle<Node>, f: impl FnOnce(&mut Self)) -> NodeId {
249        self.push_group(options, WidgetTreeNodeKind::Tree { state }, f)
250    }
251
252    /// Adds an unkeyed row flow group.
253    pub fn row(&mut self, widths: &[SizePolicy], height: SizePolicy, f: impl FnOnce(&mut Self)) -> NodeId {
254        self.row_with(NodeOptions::new(), widths, height, f)
255    }
256
257    /// Adds a row flow group with optional identity and placement metadata.
258    pub fn row_with(&mut self, options: NodeOptions, widths: &[SizePolicy], height: SizePolicy, f: impl FnOnce(&mut Self)) -> NodeId {
259        self.push_group(options, WidgetTreeNodeKind::Row { widths: widths.to_vec(), height }, f)
260    }
261
262    /// Adds an unkeyed grid flow group.
263    pub fn grid(&mut self, widths: &[SizePolicy], heights: &[SizePolicy], f: impl FnOnce(&mut Self)) -> NodeId {
264        self.grid_with(NodeOptions::new(), widths, heights, f)
265    }
266
267    /// Adds a grid flow group with optional identity and placement metadata.
268    pub fn grid_with(&mut self, options: NodeOptions, widths: &[SizePolicy], heights: &[SizePolicy], f: impl FnOnce(&mut Self)) -> NodeId {
269        self.push_group(
270            options,
271            WidgetTreeNodeKind::Grid {
272                widths: widths.to_vec(),
273                heights: heights.to_vec(),
274            },
275            f,
276        )
277    }
278
279    /// Adds an unkeyed nested column scope.
280    pub fn column(&mut self, f: impl FnOnce(&mut Self)) -> NodeId {
281        self.column_with(NodeOptions::new(), f)
282    }
283
284    /// Adds a nested column scope with optional identity and placement metadata.
285    pub fn column_with(&mut self, options: NodeOptions, f: impl FnOnce(&mut Self)) -> NodeId {
286        self.push_group(options, WidgetTreeNodeKind::Column, f)
287    }
288
289    /// Adds an unkeyed stack scope.
290    pub fn stack(&mut self, width: SizePolicy, height: SizePolicy, direction: StackDirection, f: impl FnOnce(&mut Self)) -> NodeId {
291        self.stack_with(NodeOptions::new(), width, height, direction, f)
292    }
293
294    /// Adds a stack scope with optional identity and placement metadata.
295    pub fn stack_with(&mut self, options: NodeOptions, width: SizePolicy, height: SizePolicy, direction: StackDirection, f: impl FnOnce(&mut Self)) -> NodeId {
296        self.push_group(options, WidgetTreeNodeKind::Stack { width, height, direction }, f)
297    }
298
299    fn push_leaf(&mut self, options: NodeOptions, kind: WidgetTreeNodeKind) -> NodeId {
300        let id = self.alloc_id(kind.tag(), options.key);
301        self.current_frame_mut().nodes.push(WidgetTreeNode {
302            id,
303            policy: options.policy,
304            kind,
305            children: Vec::new(),
306        });
307        id
308    }
309
310    fn push_group(&mut self, options: NodeOptions, kind: WidgetTreeNodeKind, f: impl FnOnce(&mut Self)) -> NodeId {
311        let id = self.alloc_id(kind.tag(), options.key);
312        self.frames.push(BuilderFrame::child(id.raw() as u64));
313        f(self);
314        let frame = self.frames.pop().expect("child frame missing");
315        self.current_frame_mut().nodes.push(WidgetTreeNode {
316            id,
317            policy: options.policy,
318            kind,
319            children: frame.nodes,
320        });
321        id
322    }
323
324    fn alloc_id(&mut self, tag: u8, key: Option<u64>) -> NodeId {
325        let frame = self.current_frame_mut();
326        let ordinal = frame.next_auto;
327        frame.next_auto += 1;
328
329        let mut hasher = DefaultHasher::new();
330        frame.scope_seed.hash(&mut hasher);
331        tag.hash(&mut hasher);
332        match key {
333            Some(key) => {
334                1u8.hash(&mut hasher);
335                key.hash(&mut hasher);
336            }
337            None => {
338                0u8.hash(&mut hasher);
339                ordinal.hash(&mut hasher);
340            }
341        }
342        NodeId::new(hasher.finish())
343    }
344
345    fn current_frame_mut(&mut self) -> &mut BuilderFrame {
346        self.frames.last_mut().expect("widget tree builder frame missing")
347    }
348}