Skip to main content

frust_core/app/
mod.rs

1//! The render root: the object each platform shell drives each frame.
2//!
3//! [`RenderRoot`] owns the retained [`WidgetTree`] and the previous [`View`], and runs the
4//! [`RenderRoot::rebuild`], [`RenderRoot::layout`], [`RenderRoot::paint`] and
5//! [`RenderRoot::event`] passes.
6//!
7//! Single-root: the build closure returns one `impl View<State>` of a fixed type, stored typed.
8
9mod config;
10mod dispatch;
11mod layout;
12mod metrics;
13mod paint;
14mod rebuild;
15mod semantics;
16mod session;
17#[cfg(test)]
18mod tests;
19
20use std::any::Any;
21use std::cell::{Cell, RefCell};
22use std::num::NonZeroU64;
23use std::rc::{Rc, Weak};
24use std::sync::atomic::{AtomicU64, Ordering};
25
26use kurbo::{Rect, Size};
27
28use crate::event::{CursorIcon, EventOutcome, ImeState, PointerId};
29use crate::insets::WindowInsets;
30use crate::overlay::{OverlayHit, OverlayKey};
31use crate::selection_toolbar::SelectionToolbarRequest;
32use crate::shortcuts::{ShortcutHint, ShortcutId, ShortcutTable};
33use crate::tree::WidgetTree;
34use crate::view::{ChangeFlags, View, WidgetId};
35use crate::widget::{ChildPod, PlatformViewFrame};
36
37pub use config::FocusRingPolicy;
38use layout::LayoutAudit;
39pub use metrics::{Orientation, WindowMetrics};
40
41/// Rebuild flush passes per frame: a result that navigates once, plus that page's own result.
42const MAX_PENDING_RESULT_FLUSH_PASSES: usize = 3;
43
44/// Allocates `root_identity` (`0` = no root); module-level so distinct `<State, V>` never collide.
45static NEXT_ROOT_IDENTITY: AtomicU64 = AtomicU64::new(1);
46
47/// The next focus epoch after `epoch`, skipping `0` (a never-claimed pod's stamp).
48fn advance_focus_epoch(epoch: u64) -> u64 {
49    match epoch.wrapping_add(1) {
50        0 => 1,
51        next => next,
52    }
53}
54
55/// What [`RenderRoot::event`]'s overlay pre-pass decided about one incoming event.
56enum OverlayRoute {
57    /// The overlay layer consumed the event; return this outcome unchanged.
58    Consumed(EventOutcome),
59    /// The main dispatch continues; merge this outcome into its own.
60    Continue(EventOutcome),
61}
62
63/// Owns the retained tree and drives the rebuild/layout/paint/event passes of a single-root app.
64pub struct RenderRoot<State: 'static, V: View<State>> {
65    tree: WidgetTree,
66    root_id: Option<WidgetId>,
67    prev_view: Option<V>,
68    /// Widget-id counter lent to each `BuildCtx`.
69    next_id: u64,
70    window_size: Size,
71    /// The contact whose `Down` claimed the live capture; only its own `Up`/`Cancel` clears it.
72    capture_claimant: Option<PointerId>,
73    /// Whether the live captor opted into the gesture's other contacts; `false` when uncaptured.
74    capture_contacts: bool,
75    /// Whether that opt-in came from the root widget itself rather than a pod below it.
76    contacts_captor_is_root: bool,
77    /// Whether some widget holds focus; set by a focus claim, cleared only by a session release.
78    focus_active: bool,
79    /// The branch owning the live focus session: `None` for the main tree, `Some(key)` for a
80    /// floated surface. Resolved by the paint pass from live links; other passes only clear it.
81    focus_surface: Option<OverlayKey>,
82    /// The live focus session's identity, stamped beside each pod's focus link; never `0`.
83    focus_epoch: u64,
84    /// The pod declined keys are forwarded to, with the `focus_epoch` it was recorded under.
85    key_forward: Option<(OverlayKey, u64)>,
86    /// The carrier id of this root's traversal grants (`widget::FocusGrant`); declared by no one.
87    focus_grant: ShortcutId,
88    /// Whether traversal granted the live focus; any other claim, a release or a `Down` clears it.
89    keyboard_focus: bool,
90    focus_ring_policy: FocusRingPolicy,
91    /// Whether a declined bare Tab/Shift+Tab runs traversal ([`RenderRoot::set_tab_traversal`]).
92    tab_traversal: bool,
93    hover_active: bool,
94    /// Identity of the latest hover pass; a pod's hover stamp counts only while it equals this.
95    hover_epoch: u64,
96    /// Process-unique id qualifying this root's thread-local focus and hover channels.
97    root_identity: u64,
98    cursor: CursorIcon,
99    /// Undrained clipboard write; a newer write replaces one nobody has taken yet.
100    pending_clipboard_write: Option<String>,
101    /// Whether a widget asked to read the host clipboard since the last drain.
102    pending_paste_request: bool,
103    ime_state: Option<ImeState>,
104    /// Bumped on every actual change of `focus_active` or `ime_state`, never on a same-value write.
105    focus_ime_gen: u64,
106    /// The frames the last paint published; replaced wholesale every pass.
107    platform_view_frames: Vec<PlatformViewFrame>,
108    /// The z-shield rects the last paint reported; replaced wholesale every pass.
109    input_shields: Vec<Rect>,
110    /// Dirtiness accumulated since the last [`RenderRoot::take_change_flags`].
111    pending: ChangeFlags,
112    /// The active theme, type-erased so this crate needs no `frust-theme` dependency.
113    theme: Option<Box<dyn Any>>,
114    insets: WindowInsets,
115    /// The shell's presented-frame count: a paint-time observation that dirties nothing.
116    presented_frames: Option<u64>,
117    /// Whether the GPU surface is translucent ("Mode B"); read by the platform-view hole-punch.
118    surface_translucent: bool,
119    /// Next never-reused semantics base id; starts at `2` (`0` keeps `NonZeroU64` valid, `1` is
120    /// the window node). A `Cell` because [`RenderRoot::semantics`] takes `&self`.
121    semantics_alloc: Cell<u64>,
122    root_semantics_id: Cell<Option<NonZeroU64>>,
123    /// Bumped when a rebuild, theme swap or inset change may have changed the semantics tree.
124    semantics_gen: u64,
125    /// Set when the deferred-callback flush owes a frame; the next paint reports `needs_frame`.
126    deferred_frame: bool,
127    /// The root widget's own trailing-paint bit, since it sits in no pod.
128    painted_animating: bool,
129    /// Routing half of the overlay entries the last paint registered, in paint order; no pods.
130    overlay_hits: Vec<OverlayHit>,
131    /// The shortcut declarations the last paint made, matched against unhandled keys.
132    shortcuts: ShortcutTable,
133    /// `shortcuts` as the snapshot this root's build and paint contexts hand out.
134    last_hints: Rc<[ShortcutHint]>,
135    /// The focused field's selection-toolbar request from the last paint; a release clears it.
136    selection_toolbar: Option<SelectionToolbarRequest>,
137    /// Bumped on every actual change of `selection_toolbar`, a clear included.
138    selection_toolbar_gen: u64,
139    /// Bumped by a theme swap, an inset change, a resize, or a dirty floated surface.
140    layout_epoch: u64,
141    /// A pod painted under another paint epoch is never reported clean.
142    paint_epoch: u64,
143    /// Serial of the most recent paint (`0` before the first).
144    paint_serial: u64,
145    /// The hover claimant's `inspect_id` (`0` for the root widget); `None` while nothing hovers.
146    hover_link: Option<u64>,
147    /// `(hover_link, focus_epoch)` as the last paint saw them; a change bumps `paint_epoch`.
148    painted_links: (Option<u64>, u64),
149    retained_layers: bool,
150    /// The renderer's resident-key ledger as of the previous frame.
151    retained_ledger: crate::retained::RetainedLedger,
152    layout_skip: bool,
153    layout_audit: LayoutAudit,
154    /// Weak handles to the floated pods the last paint drew. Their owners lay them out, so
155    /// [`RenderRoot::layout`] bumps the layout epoch when any is dirty.
156    overlay_pods: Vec<Weak<RefCell<ChildPod>>>,
157    _state: core::marker::PhantomData<fn(&mut State)>,
158}
159
160impl<State: 'static, V: View<State>> RenderRoot<State, V> {
161    /// Create an empty render root with no widget yet built.
162    pub fn new() -> Self {
163        Self {
164            tree: WidgetTree::new(),
165            root_id: None,
166            prev_view: None,
167            next_id: 0,
168            window_size: Size::ZERO,
169            capture_claimant: None,
170            capture_contacts: false,
171            contacts_captor_is_root: false,
172            focus_active: false,
173            focus_surface: None,
174            focus_epoch: 1,
175            key_forward: None,
176            focus_grant: ShortcutId::next(),
177            keyboard_focus: false,
178            focus_ring_policy: FocusRingPolicy::KeyboardOnly,
179            tab_traversal: true,
180            hover_active: false,
181            hover_epoch: 1,
182            root_identity: NEXT_ROOT_IDENTITY.fetch_add(1, Ordering::Relaxed),
183            cursor: CursorIcon::Default,
184            pending_clipboard_write: None,
185            pending_paste_request: false,
186            ime_state: None,
187            focus_ime_gen: 0,
188            platform_view_frames: Vec::new(),
189            input_shields: Vec::new(),
190            pending: ChangeFlags::NONE,
191            theme: None,
192            insets: WindowInsets::default(),
193            presented_frames: None,
194            surface_translucent: false,
195            semantics_alloc: Cell::new(2),
196            root_semantics_id: Cell::new(None),
197            semantics_gen: 0,
198            deferred_frame: false,
199            painted_animating: false,
200            overlay_hits: Vec::new(),
201            shortcuts: ShortcutTable::default(),
202            last_hints: Rc::from(Vec::new()),
203            selection_toolbar: None,
204            selection_toolbar_gen: 0,
205            layout_epoch: 1,
206            paint_epoch: 1,
207            paint_serial: 0,
208            hover_link: None,
209            painted_links: (None, 1),
210            retained_layers: true,
211            retained_ledger: crate::retained::RetainedLedger::empty(),
212            layout_skip: true,
213            layout_audit: LayoutAudit::default(),
214            overlay_pods: Vec::new(),
215            _state: core::marker::PhantomData,
216        }
217    }
218
219    /// The last paint's shortcut declarations `filter` keeps, in paint order: a palette's list.
220    pub fn shortcut_hints(
221        &self,
222        mut filter: impl FnMut(&ShortcutHint) -> bool,
223    ) -> Vec<ShortcutHint> {
224        self.last_hints
225            .iter()
226            .filter(|hint| filter(hint))
227            .cloned()
228            .collect()
229    }
230}
231
232impl<State: 'static, V: View<State>> Default for RenderRoot<State, V> {
233    fn default() -> Self {
234        Self::new()
235    }
236}