Skip to main content

lgui_core/core/view/
builder.rs

1use super::{
2    AnimationRegistry, ComponentStateStore, ComponentTree, ContextRegistry, EffectRegistry,
3    HookStateStore, HostTree, InteractionRole, LayoutSpec, RenderCx, RenderPhase, TextStyle,
4    UiElement, UiId, UiInteractionState, UiNode, UiNodeKind, UiRect, UiRenderContext, UiScale,
5    UiScope, UiTaskSpawner, UiUpdateQueue, VisualStyle,
6};
7use std::collections::{HashMap, HashSet};
8use std::sync::Arc;
9
10pub trait RootComponent {
11    fn render_root(self, cx: &mut RenderCx<'_, '_>) -> super::Element;
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
15pub struct HostProjectionMetrics {
16    pub visited_nodes: usize,
17    pub reused_component_roots: usize,
18}
19
20pub struct HostTreeBuilder {
21    tree: HostTree,
22    parent_stack: Vec<UiId>,
23    retained: bool,
24    fresh_owners: HashSet<super::ComponentId>,
25    seen_by_owner: HashMap<super::ComponentId, HashSet<UiId>>,
26    structure_changed: bool,
27    projection_metrics: HostProjectionMetrics,
28}
29
30struct RenderTransaction<'a> {
31    component_states: &'a ComponentStateStore,
32    component_tree: &'a ComponentTree,
33    contexts: &'a ContextRegistry,
34    hook_states: &'a HookStateStore,
35    effects: &'a EffectRegistry,
36    committed: bool,
37}
38
39impl RenderTransaction<'_> {
40    fn commit(&mut self) {
41        self.committed = true;
42    }
43}
44
45impl Drop for RenderTransaction<'_> {
46    fn drop(&mut self) {
47        if self.committed {
48            return;
49        }
50        self.component_tree.abort_render();
51        self.contexts.abort_render(self.component_tree);
52        self.hook_states.abort_render(self.component_tree);
53        self.component_states.abort_frame();
54        self.effects.abort_frame();
55    }
56}
57
58impl HostTreeBuilder {
59    pub fn new() -> Self {
60        Self {
61            tree: HostTree::new(),
62            parent_stack: Vec::new(),
63            retained: false,
64            fresh_owners: HashSet::new(),
65            seen_by_owner: HashMap::new(),
66            structure_changed: false,
67            projection_metrics: HostProjectionMetrics::default(),
68        }
69    }
70
71    pub fn from_retained(tree: HostTree) -> Self {
72        Self {
73            tree,
74            parent_stack: Vec::new(),
75            retained: true,
76            fresh_owners: HashSet::new(),
77            seen_by_owner: HashMap::new(),
78            structure_changed: false,
79            projection_metrics: HostProjectionMetrics::default(),
80        }
81    }
82
83    pub fn push(&mut self, mut node: UiNode) -> UiId {
84        if node.parent.is_none() {
85            node.parent = self.parent_stack.last().cloned();
86        }
87        let id = node.id.clone();
88        self.tree.push(node);
89        id
90    }
91
92    pub(crate) fn mount_element(&mut self, element: UiElement) -> UiId {
93        self.projection_metrics.visited_nodes += 1;
94        let (mut node, children, boundary) = element.into_parts();
95        if node.parent.is_none() {
96            node.parent = self.parent_stack.last().cloned();
97        }
98        let id = node.id.clone();
99        let previous_children = self
100            .tree
101            .node(&id)
102            .map(|current| current.children.clone())
103            .unwrap_or_default();
104        let retain_children = boundary.is_some_and(|boundary| boundary.retain_children);
105        if let Some(boundary) = boundary {
106            if !boundary.retain_children {
107                self.fresh_owners.insert(boundary.id);
108            }
109        }
110        node.children = previous_children.clone();
111        if retain_children {
112            self.projection_metrics.reused_component_roots += 1;
113        }
114        if let Some(owner) = node.component_owner {
115            self.seen_by_owner
116                .entry(owner)
117                .or_default()
118                .insert(id.clone());
119        }
120        if self.retained {
121            self.tree.upsert(node);
122        } else {
123            self.tree.push(node);
124        }
125        if !retain_children {
126            self.parent_stack.push(id.clone());
127            let next_children = children
128                .iter()
129                .cloned()
130                .map(|child| self.mount_element(child))
131                .collect::<Vec<_>>();
132            self.parent_stack.pop();
133            self.structure_changed |= previous_children != next_children;
134            self.tree.set_children(&id, next_children);
135        }
136        id
137    }
138
139    pub fn node(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect) -> UiId {
140        self.push(UiNode::new(id, kind, rect))
141    }
142
143    pub fn interactive(
144        &mut self,
145        id: UiId,
146        kind: UiNodeKind,
147        rect: UiRect,
148        interaction: InteractionRole,
149    ) -> UiId {
150        self.push(UiNode::new(id, kind, rect).interaction(interaction))
151    }
152
153    pub fn styled(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect, style: VisualStyle) -> UiId {
154        self.push(UiNode::new(id, kind, rect).style(style))
155    }
156
157    pub fn text(
158        &mut self,
159        id: UiId,
160        rect: UiRect,
161        text: impl Into<std::borrow::Cow<'static, str>>,
162        style: TextStyle,
163    ) -> UiId {
164        self.push(UiNode::new(id, UiNodeKind::Text, rect).text(text, style))
165    }
166
167    pub fn laid_out(
168        &mut self,
169        id: UiId,
170        kind: UiNodeKind,
171        rect: UiRect,
172        layout: LayoutSpec,
173    ) -> UiId {
174        self.push(UiNode::new(id, kind, rect).layout(layout))
175    }
176
177    pub fn in_phase(
178        &mut self,
179        id: UiId,
180        kind: UiNodeKind,
181        rect: UiRect,
182        phase: RenderPhase,
183    ) -> UiId {
184        self.push(UiNode::new(id, kind, rect).render_phase(phase))
185    }
186
187    pub fn with_parent<T>(&mut self, id: UiId, build: impl FnOnce(&mut Self) -> T) -> T {
188        self.parent_stack.push(id);
189        let result = build(self);
190        self.parent_stack.pop();
191        result
192    }
193
194    pub fn element(&mut self, element: UiElement) -> UiId {
195        element.mount(self)
196    }
197
198    pub fn mount(
199        &mut self,
200        component: impl RootComponent,
201        viewport: UiRect,
202        interaction: &UiInteractionState,
203        animations: &AnimationRegistry,
204        component_states: &ComponentStateStore,
205        component_tree: &ComponentTree,
206        contexts: &ContextRegistry,
207        hook_states: &HookStateStore,
208        hook_updates: &Arc<UiUpdateQueue>,
209        task_spawner: Option<&UiTaskSpawner>,
210        effects: &EffectRegistry,
211        scale: UiScale,
212    ) -> UiId {
213        component_states.begin_frame();
214        component_tree.begin_render();
215        contexts.begin_render();
216        hook_states.begin_render();
217        effects.begin_frame();
218        let mut transaction = RenderTransaction {
219            component_states,
220            component_tree,
221            contexts,
222            hook_states,
223            effects,
224            committed: false,
225        };
226        let scope = UiScope::new("ui");
227        let context = UiRenderContext::new(
228            interaction,
229            animations,
230            component_states,
231            component_tree,
232            contexts,
233            hook_states,
234            hook_updates,
235            task_spawner,
236            effects,
237            viewport,
238            scale,
239        );
240        let root = {
241            let mut cx = RenderCx::new(&scope, &context);
242            let _current_context = context.contexts().enter_current(cx.component_id());
243            let view = component.render_root(&mut cx);
244            let root = cx.component_id();
245            self.element(
246                cx.compile(view)
247                    .claim_component_owner(root)
248                    .component_boundary(root),
249            )
250        };
251        contexts.validate_listener_hooks();
252        component_states.end_frame();
253        component_tree.end_render();
254        self.fresh_owners
255            .extend(component_tree.take_executed_in_render());
256        for owner in self.fresh_owners.clone() {
257            let keep = self.seen_by_owner.get(&owner).cloned().unwrap_or_default();
258            self.structure_changed |= self.tree.retain_owner_nodes(owner, &keep);
259        }
260        self.structure_changed |= self.tree.prune_dead_component_owners(component_tree);
261        if self.structure_changed {
262            self.tree.reorder_by_hierarchy();
263        }
264        contexts.commit_listener_effects(component_tree, effects);
265        contexts.end_render(component_tree);
266        hook_states.end_render(component_tree);
267        effects.end_frame(component_tree);
268        transaction.commit();
269        root
270    }
271
272    pub fn finish(self) -> HostTree {
273        self.tree
274    }
275
276    pub fn projection_metrics(&self) -> HostProjectionMetrics {
277        self.projection_metrics
278    }
279}
280
281impl Default for HostTreeBuilder {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287#[cfg(test)]
288#[path = "builder_test.rs"]
289mod tests;