Skip to main content

frust_core/app/
rebuild.rs

1//! The rebuild pass on [`RenderRoot`] — build closure, view diff, deferred-callback flush and
2//! unmount drains — plus the shell-facing drains and tree inspection that read its results.
3
4use std::rc::Rc;
5
6use kurbo::Rect;
7
8use super::{MAX_PENDING_RESULT_FLUSH_PASSES, RenderRoot};
9use crate::event::InputEvent;
10use crate::tree::{InspectNode, WidgetPod, WidgetTree};
11use crate::view::{BuildCtx, ChangeFlags, View, WidgetId};
12use crate::widget::PlatformViewFrame;
13
14impl<State: 'static, V: View<State>> RenderRoot<State, V> {
15    /// The [`PlatformViewFrame`]s the most recent paint published, in paint order (never stale).
16    pub fn platform_view_frames(&self) -> &[PlatformViewFrame] {
17        &self.platform_view_frames
18    }
19
20    /// The z-shield rects the most recent paint reported, in paint order (never stale).
21    pub fn input_shields(&self) -> &[Rect] {
22        &self.input_shields
23    }
24
25    /// Drain the slot ids whose `platform_view` widgets were torn down since the last call, so a
26    /// shell retires their native views at once. Each id is reported exactly once; a merely culled
27    /// slot never appears (culling runs no `teardown`).
28    pub fn take_retired_platform_views(&mut self) -> Vec<u64> {
29        crate::widget::take_retired_slots()
30    }
31
32    /// Take (and clear) the dirtiness accumulated since the last call.
33    pub fn take_change_flags(&mut self) -> ChangeFlags {
34        let flags = self.pending;
35        self.pending = ChangeFlags::NONE;
36        flags
37    }
38
39    /// Non-draining peek: whether any `LAYOUT`/`PAINT` bit is pending. A frame gate reads it before
40    /// deciding, so a skipped frame leaves the flags for the next running frame to drain.
41    pub fn has_pending_change_flags(&self) -> bool {
42        !self.pending.is_empty()
43    }
44
45    /// The root widget id, once built.
46    pub fn root_id(&self) -> Option<WidgetId> {
47        self.root_id
48    }
49
50    /// Shared access to the retained tree (for the shell / tests).
51    pub fn tree(&self) -> &WidgetTree {
52        &self.tree
53    }
54
55    /// A read-only pre-order snapshot of the retained tree, container-owned pods included, for
56    /// tooling: per node its id, parent, children, type name, debug label and absolute border box.
57    /// Bounds are the last layout pass's (all zero before the first).
58    pub fn inspect(&self) -> Vec<InspectNode> {
59        self.tree.inspect()
60    }
61
62    /// Per-node layout-dirty state in [`RenderRoot::inspect`]'s order and ids, for tooling/tests.
63    pub fn inspect_layout_dirty(&self) -> Vec<(WidgetId, bool)> {
64        self.tree.inspect_layout_dirty()
65    }
66
67    /// Per-node paint-dirty state, read-only — see [`WidgetTree::inspect_paint_dirty`].
68    pub fn inspect_paint_dirty(&self) -> Vec<(WidgetId, bool)> {
69        self.tree.inspect_paint_dirty()
70    }
71
72    /// Run the build closure, then build (first call) or rebuild the root widget, returning what
73    /// changed. The closure re-runs in full every rebuild, so it must be cheap.
74    ///
75    /// # Deferred-callback flush
76    ///
77    /// While a structural op has raised `mark_pending_result_flush`, each pass dispatches one
78    /// [`InputEvent::Housekeeping`] broadcast (where `&mut State` is in scope) and re-diffs, capped
79    /// by `MAX_PENDING_RESULT_FLUSH_PASSES`: past it the mark stays raised and one more frame is
80    /// requested. A broadcast's `needs_redraw` requests that frame too.
81    pub fn rebuild(
82        &mut self,
83        build: &mut impl FnMut(&mut State) -> V,
84        state: &mut State,
85    ) -> ChangeFlags {
86        // First: a pod the diff severs reads this root's session from its destructor.
87        self.publish_focus_session();
88        let view = build(state);
89        let mut flags = self.rebuild_view(view);
90
91        let mut passes = 0usize;
92        while crate::event::take_pending_result_flush() {
93            if passes >= MAX_PENDING_RESULT_FLUSH_PASSES {
94                // The work is still owed: re-mark it and request a frame instead of spinning.
95                crate::event::mark_pending_result_flush();
96                flags |= ChangeFlags::PAINT;
97                self.deferred_frame = true;
98                break;
99            }
100            // A redraw-only callback re-diffs to `NONE`, so its outcome must raise the wake itself.
101            let outcome = self.event(state, &InputEvent::Housekeeping);
102            if outcome.needs_redraw {
103                flags |= ChangeFlags::PAINT;
104                self.deferred_frame = true;
105            }
106            let view = build(state);
107            flags |= self.rebuild_view(view);
108            passes += 1;
109        }
110
111        // Generic-unmount focus release, after the loop so one release covers every pass. Because
112        // `rebuild_view` seeds the chain, a mark means a live session lost its owner, never a stale
113        // flag in an already-blurred branch.
114        if crate::event::take_focus_orphaned() {
115            self.release_focus_session();
116        }
117
118        // Generic-unmount hover release. The dying pod's destructor raises the mark (qualified by
119        // `root_identity`, as another root may share the thread), so this flags `PAINT` itself: a
120        // container outside this workspace may drop a pod without reporting it.
121        if crate::event::take_hover_orphaned(self.root_identity) && self.hover_active {
122            self.end_hover_link();
123            flags |= ChangeFlags::PAINT;
124        }
125
126        self.pending |= flags;
127        // Any change may have changed the semantics tree.
128        if !flags.is_empty() {
129            self.semantics_gen = self.semantics_gen.wrapping_add(1);
130        }
131        flags
132    }
133
134    /// The rebuild body. Seeds the diff's focus chain ([`BuildCtx::has_focus`]) with "is there a
135    /// session to lose" (`focus_active` or a standing IME surface), so a reconciler marks an orphan
136    /// only when the whole chain down to it is live.
137    fn rebuild_view(&mut self, view: V) -> ChangeFlags {
138        let session_live = self.focus_active || self.ime_state.is_some();
139        match (self.root_id, self.prev_view.take()) {
140            // Reconcile against the previous view of the same type.
141            (Some(root_id), Some(prev)) => {
142                let mut ctx = BuildCtx::new(&mut self.next_id)
143                    .with_shortcut_hints(Rc::clone(&self.last_hints));
144                ctx.set_has_focus(session_live);
145                let flags = {
146                    let pod = self
147                        .tree
148                        .pod_mut(root_id)
149                        .expect("root pod present when root_id is set");
150                    let element = pod
151                        .widget_mut()
152                        .downcast_mut::<V::Element>()
153                        .expect("root widget type matches its originating view");
154                    view.rebuild(&prev, element, &mut ctx)
155                };
156                if let Some(pod) = self.tree.pod_mut(root_id) {
157                    pod.merge_flags(flags);
158                }
159                self.prev_view = Some(view);
160                flags
161            }
162            // First build: materialise the widget and insert it as the root.
163            _ => {
164                let mut ctx = BuildCtx::new(&mut self.next_id)
165                    .with_shortcut_hints(Rc::clone(&self.last_hints));
166                // Moot on a first build, but the root always seeds the chain.
167                ctx.set_has_focus(session_live);
168                let id = ctx.alloc_id();
169                let element = view.build(&mut ctx);
170                // `new_typed` also records `V::Element`'s type name for introspection.
171                let pod = WidgetPod::new_typed(id, element);
172                let root_id = self.tree.insert_root(pod);
173                self.root_id = Some(root_id);
174                self.prev_view = Some(view);
175                ChangeFlags::LAYOUT | ChangeFlags::PAINT
176            }
177        }
178    }
179}