Skip to main content

cranpose_app_shell/
surface.rs

1//! Root surfaces: what the shell keeps per window.
2//!
3//! The composition is one tree. Each window the app shows is a root in that
4//! tree: the composition root for the primary window, a node carrying
5//! `Modifier::window_root` for every other. A [`RootSurface`] is the shell's
6//! bookkeeping for one such root: the renderer that draws it, the viewport it
7//! draws into, the pointer that hovers it, the gesture it tracks, the
8//! snapshots a test or a platform reads for it, and the dirt that says whether
9//! it owes the display a frame. A [`SurfaceMut`] borrows one surface together
10//! with the app, which is how a platform delivers a window's events and reads
11//! a window's frame.
12
13use std::{cell::RefCell, fmt::Debug, rc::Rc};
14
15use cranpose_core::{NodeId, collections::map::HashSet};
16use cranpose_foundation::{PointerButtons, PointerSource, RotaryScrollEvent};
17use cranpose_render_common::Renderer;
18use cranpose_ui::{
19    LayoutTree, PlatformTextInputHandler, SemanticsTree, pointer_icon_session::PointerIconState,
20};
21use cranpose_ui_graphics::{Point, PointerIcon, Size};
22use web_time::Instant;
23
24use crate::{
25    AppShell, DevOverlayControl, FramePacingMode, FrameRatePreference, FrameSchedule,
26    FrameScheduler, FrameUpdateResult, PlatformFrameDriver, ShellApp,
27    hit_path_tracker::{HitPathTracker, PointerId},
28};
29
30/// Names a root surface of the app: the primary window, or a window root
31/// declared with [`Modifier::window_root`](cranpose_ui::Modifier::window_root)
32/// by the id given there.
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum RootId {
35    /// The composition root, drawn in the app's first window.
36    Primary,
37    /// The node carrying `Modifier::window_root` with this id.
38    Window(u64),
39}
40
41/// The shell's bookkeeping for one root.
42///
43/// The primary surface draws the composition root. A window surface draws the
44/// window root node registered under its id, and draws nothing while that
45/// node is not in the tree. Everything here is what an OS owns per window:
46/// the framebuffer, the viewport, the pointer, the cursor image, the soft
47/// keyboard, the frame the window is asked for.
48pub struct RootSurface<R: Renderer> {
49    pub(crate) id: RootId,
50    pub(crate) root: Option<NodeId>,
51    pub(crate) renderer: R,
52    pub(crate) cursor: (f32, f32),
53    pub(crate) viewport: (f32, f32),
54    pub(crate) buffer_size: (u32, u32),
55    pub(crate) layout_tree: Option<LayoutTree>,
56    pub(crate) semantics_tree: Option<SemanticsTree>,
57    pub(crate) frame_rate_preference: FrameRatePreference,
58    pub(crate) scene_dirty: bool,
59    pub(crate) scoped_layout_scene_nodes: Vec<NodeId>,
60    pub(crate) retained_visual_nodes: HashSet<NodeId>,
61    pub(crate) is_dirty: bool,
62    pub(crate) buttons_pressed: PointerButtons,
63    pub(crate) pointer_source: PointerSource,
64    pub(crate) hit_path_tracker: HitPathTracker,
65    pub(crate) hovered_nodes: Vec<NodeId>,
66    pub(crate) on_rotary_scroll: Option<Rc<dyn Fn(RotaryScrollEvent) -> bool>>,
67    pub(crate) dev_overlay_controls: Vec<DevOverlayControl>,
68    pub(crate) inspector: crate::inspector::DeveloperInspector,
69    pub(crate) dev_overlay_text: String,
70    pub(crate) dev_overlay_last_refresh: Option<Instant>,
71    pub(crate) dev_overlay_viewport: Option<Size>,
72    pub(crate) frame_scheduler: FrameScheduler,
73    pub(crate) pointer_icon: PointerIconState,
74    pub(crate) last_update: FrameUpdateResult,
75    pub(crate) frame_owed: bool,
76    pub(crate) screen_origin: Option<Point>,
77}
78
79impl<R: Renderer> RootSurface<R> {
80    pub(crate) fn new(
81        id: RootId,
82        renderer: R,
83        buffer_size: (u32, u32),
84        viewport: (f32, f32),
85    ) -> Self {
86        Self {
87            id,
88            root: None,
89            renderer,
90            cursor: (0.0, 0.0),
91            viewport,
92            buffer_size,
93            layout_tree: None,
94            semantics_tree: None,
95            frame_rate_preference: FrameRatePreference::default(),
96            scene_dirty: true,
97            scoped_layout_scene_nodes: Vec::new(),
98            retained_visual_nodes: HashSet::new(),
99            is_dirty: true,
100            buttons_pressed: PointerButtons::NONE,
101            pointer_source: PointerSource::Unknown,
102            hit_path_tracker: HitPathTracker::new(),
103            hovered_nodes: Vec::new(),
104            on_rotary_scroll: None,
105            dev_overlay_controls: Vec::new(),
106            inspector: crate::inspector::DeveloperInspector::default(),
107            dev_overlay_text: String::new(),
108            dev_overlay_last_refresh: None,
109            dev_overlay_viewport: None,
110            frame_scheduler: FrameScheduler::default(),
111            pointer_icon: PointerIconState::new(),
112            last_update: FrameUpdateResult::default(),
113            frame_owed: false,
114            screen_origin: None,
115        }
116    }
117
118    pub(crate) fn root_node(&self, app: &ShellApp) -> Option<NodeId> {
119        match self.id {
120            RootId::Primary => app.composition.root(),
121            RootId::Window(_) => self.root,
122        }
123    }
124
125    pub(crate) fn owns_nodes_under(&self, window_root: Option<NodeId>) -> bool {
126        match self.id {
127            RootId::Primary => window_root.is_none(),
128            RootId::Window(_) => self.root.is_some() && self.root == window_root,
129        }
130    }
131
132    pub(crate) fn viewport_size(&self) -> Size {
133        Size {
134            width: self.viewport.0,
135            height: self.viewport.1,
136        }
137    }
138
139    pub(crate) fn screen_point_inside(&self, screen: Point) -> Option<Point> {
140        let origin = self.screen_origin?;
141        let local = Point {
142            x: screen.x - origin.x,
143            y: screen.y - origin.y,
144        };
145        (local.x >= 0.0
146            && local.y >= 0.0
147            && local.x <= self.viewport.0
148            && local.y <= self.viewport.1)
149            .then_some(local)
150    }
151
152    pub(crate) fn set_root(&mut self, root: Option<NodeId>) {
153        if self.root == root {
154            return;
155        }
156        self.root = root;
157        self.forget_snapshots();
158        self.scoped_layout_scene_nodes.clear();
159        self.retained_visual_nodes.clear();
160        self.hit_path_tracker.clear();
161        self.hovered_nodes.clear();
162        self.buttons_pressed = PointerButtons::NONE;
163        self.scene_dirty = true;
164        self.is_dirty = true;
165    }
166
167    pub(crate) fn forget_snapshots(&mut self) {
168        self.layout_tree = None;
169        self.semantics_tree = None;
170    }
171
172    pub(crate) fn has_active_pointer_gesture(&self) -> bool {
173        self.buttons_pressed != PointerButtons::NONE
174            && self.hit_path_tracker.has_path(PointerId::PRIMARY)
175    }
176
177    pub(crate) fn renderer_warmup_due(&self, app: &ShellApp) -> bool {
178        self.renderer.needs_frame_warmup() && !app.runtime.runtime_handle().has_frame_callbacks()
179    }
180
181    pub(crate) fn needs_redraw_in_context(&self, app: &ShellApp) -> bool {
182        app.has_stale_work_in_context()
183            || self.is_dirty
184            || self.scene_dirty
185            || self.renderer_warmup_due(app)
186    }
187
188    pub(crate) fn compute_frame_schedule(
189        &self,
190        app: &ShellApp,
191        surfaces_dirty: bool,
192    ) -> FrameSchedule {
193        let app_context = Rc::clone(&app.app_context);
194        let (needs_update, needs_frame) = app_context.enter(|| {
195            let needs_frame = self.is_dirty
196                || self.scene_dirty
197                || app.wants_frame_in_context()
198                || self.has_active_pointer_gesture()
199                || self.renderer_warmup_due(app);
200            (app.needs_ui_update_in_context(surfaces_dirty), needs_frame)
201        });
202        FrameSchedule {
203            needs_update,
204            needs_frame,
205            next_deadline: app.next_event_time(),
206        }
207    }
208
209    pub(crate) fn invalidate_dev_overlay_text(&mut self) {
210        self.dev_overlay_text.clear();
211        self.dev_overlay_last_refresh = None;
212        self.dev_overlay_viewport = None;
213    }
214
215    pub(crate) fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
216        self.dev_overlay_controls
217            .iter()
218            .find(|control| control.mode == mode)
219            .map(|control| {
220                (
221                    control.bounds.x + control.bounds.width * 0.5,
222                    control.bounds.y + control.bounds.height * 0.5,
223                )
224            })
225    }
226
227    pub(crate) fn layout_tree_in_context(&mut self, app: &mut ShellApp) -> Option<&LayoutTree> {
228        if self.layout_tree.is_none() {
229            let root = self.root_node(app)?;
230            let mut applier = app.composition.applier_mut();
231            match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
232                Ok(layout_tree) => {
233                    self.layout_tree = layout_tree;
234                }
235                Err(err) => {
236                    log::debug!("failed to build layout snapshot: {err}");
237                    return None;
238                }
239            }
240        }
241        self.layout_tree.as_ref()
242    }
243
244    pub(crate) fn semantics_tree_in_context(
245        &mut self,
246        app: &mut ShellApp,
247    ) -> Option<&SemanticsTree> {
248        if !app.semantics_enabled && !self.inspector.state.open {
249            return None;
250        }
251        self.semantics_tree_for_input(app)
252    }
253
254    pub(crate) fn semantics_tree_for_input(
255        &mut self,
256        app: &mut ShellApp,
257    ) -> Option<&SemanticsTree> {
258        let root = self.root_node(app)?;
259        let semantics_dirty = {
260            let mut applier = app.composition.applier_mut();
261            cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
262                log::debug!("failed to check semantics dirty status for root #{root}: {err}");
263                true
264            })
265        };
266        if self.semantics_tree.is_none() || semantics_dirty {
267            let mut applier = app.composition.applier_mut();
268            match cranpose_ui::build_semantics_tree_from_applier(&mut applier, root) {
269                Ok(semantics_tree) => {
270                    self.semantics_tree = semantics_tree;
271                }
272                Err(err) => {
273                    log::debug!("failed to build semantics snapshot: {err}");
274                    return None;
275                }
276            }
277        }
278        self.semantics_tree.as_ref()
279    }
280}
281
282#[derive(Default)]
283pub(crate) struct TextInputRoutes {
284    active: Option<RootId>,
285    shown: Option<RootId>,
286    handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
287}
288
289impl TextInputRoutes {
290    pub(crate) fn active(&self) -> RootId {
291        self.active.unwrap_or(RootId::Primary)
292    }
293
294    pub(crate) fn set_active(&mut self, root: RootId) {
295        self.active = Some(root);
296    }
297
298    pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
299        self.remove(root);
300        self.handlers.push((root, handler));
301    }
302
303    pub(crate) fn remove(&mut self, root: RootId) {
304        self.handlers.retain(|(id, _)| *id != root);
305        if self.shown == Some(root) {
306            self.shown = None;
307        }
308    }
309
310    fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
311        self.handlers
312            .iter()
313            .find(|(id, _)| *id == root)
314            .map(|(_, handler)| Rc::clone(handler))
315    }
316
317    fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
318        let active = self.active();
319        let handler = self.handler(active);
320        if handler.is_some() {
321            self.shown = Some(active);
322        }
323        handler
324    }
325
326    fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
327        let target = self.shown.take().unwrap_or_else(|| self.active());
328        self.handler(target)
329    }
330}
331
332pub(crate) struct TextInputRouter {
333    pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
334}
335
336impl PlatformTextInputHandler for TextInputRouter {
337    fn show_keyboard(&self) {
338        let handler = self.routes.borrow_mut().take_show_target();
339        if let Some(handler) = handler {
340            handler.show_keyboard();
341        }
342    }
343
344    fn hide_keyboard(&self) {
345        let handler = self.routes.borrow_mut().take_hide_target();
346        if let Some(handler) = handler {
347            handler.hide_keyboard();
348        }
349    }
350}
351
352pub(crate) fn partition_nodes_by_surface<R: Renderer>(
353    app: &mut ShellApp,
354    surfaces: &[RootSurface<R>],
355    nodes: Vec<NodeId>,
356) -> Vec<Vec<NodeId>> {
357    let mut buckets: Vec<Vec<NodeId>> = surfaces.iter().map(|_| Vec::new()).collect();
358    let Some(primary_root) = app.composition.root() else {
359        return buckets;
360    };
361    let mut applier = app.composition.applier_mut();
362    for node in nodes {
363        let Some(node) = applier.scene_node_attached_to(node, primary_root) else {
364            continue;
365        };
366        let owner = cranpose_ui::nearest_window_root(&mut applier, node);
367        if let Some(index) = surfaces
368            .iter()
369            .position(|surface| surface.owns_nodes_under(owner))
370        {
371            buckets[index].push(node);
372        }
373    }
374    buckets
375}
376
377/// One surface borrowed together with its shell: the handle a platform
378/// delivers a window's events through and reads a window's frame from.
379///
380/// [`AppShell::surface`] hands one out per root. Every method of [`AppShell`]
381/// that names no root acts on the primary surface through the same code, so
382/// a single-window platform never sees this type.
383pub struct SurfaceMut<'a, R: Renderer> {
384    pub(crate) shell: &'a mut AppShell<R>,
385    pub(crate) index: usize,
386}
387
388impl<'a, R> SurfaceMut<'a, R>
389where
390    R: Renderer,
391    R::Error: Debug,
392{
393    pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
394        Self { shell, index }
395    }
396
397    /// The whole app, for what a window's event needs beyond its surface:
398    /// the clipboard, the dev options, a debug report.
399    pub fn shell(&mut self) -> &mut AppShell<R> {
400        self.shell
401    }
402
403    pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
404        &mut self.shell.app
405    }
406
407    pub(crate) fn shell_app_ref(&self) -> &ShellApp {
408        &self.shell.app
409    }
410
411    pub(crate) fn surface(&self) -> &RootSurface<R> {
412        &self.shell.surfaces[self.index]
413    }
414
415    pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
416        &mut self.shell.surfaces[self.index]
417    }
418
419    pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
420        let shell = &mut *self.shell;
421        (&mut shell.app, &mut shell.surfaces[self.index])
422    }
423
424    /// Which root this surface draws.
425    pub fn id(&self) -> RootId {
426        self.surface().id
427    }
428
429    /// The node this surface draws from, when it has one.
430    pub fn root(&self) -> Option<NodeId> {
431        let surface = self.surface();
432        surface.root_node(self.shell_app_ref())
433    }
434
435    /// The renderer that draws this surface.
436    pub fn renderer(&mut self) -> &mut R {
437        &mut self.surface_mut().renderer
438    }
439
440    /// The scene this surface last built.
441    pub fn scene(&self) -> &R::Scene {
442        self.surface().renderer.scene()
443    }
444
445    /// Sets the logical size this surface lays out and draws into.
446    ///
447    /// The primary surface's viewport is the composition root's constraints.
448    /// A window surface's viewport is what its renderer draws into; the
449    /// window root lays out to the size its descriptor reports, which the
450    /// platform keeps equal to this. The next update lays out and renders;
451    /// [`AppShell::set_viewport`] additionally runs that frame at once.
452    pub fn set_viewport(&mut self, width: f32, height: f32) {
453        self.surface_mut().viewport = (width, height);
454        match self.id() {
455            RootId::Primary => self.shell_app().request_forced_layout_pass(),
456            RootId::Window(_) => {
457                if let Some(root) = self.root() {
458                    let app_context = Rc::clone(&self.shell_app_ref().app_context);
459                    app_context.enter(|| cranpose_ui::schedule_measure_repass(root));
460                }
461                self.shell_app().request_layout_pass();
462            }
463        }
464        self.surface_mut().scene_dirty = true;
465        self.mark_dirty();
466    }
467
468    /// Tells the shell where the window drawing this surface sits on the
469    /// screen, in logical pixels, so pointer events can carry a
470    /// [`screen_position`](cranpose_foundation::PointerEvent::screen_position).
471    /// A platform sets it when the window moves and before it delivers a
472    /// pointer sample; `None` says the platform does not know.
473    pub fn set_screen_origin(&mut self, origin: Option<Point>) {
474        self.surface_mut().screen_origin = origin;
475    }
476
477    /// Where the window drawing this surface sits on the screen, as the
478    /// platform last said.
479    pub fn screen_origin(&self) -> Option<Point> {
480        self.surface().screen_origin
481    }
482
483    /// The logical size this surface draws into.
484    pub fn viewport_size(&self) -> (f32, f32) {
485        self.surface().viewport
486    }
487
488    /// Sets the physical size of this surface's framebuffer.
489    pub fn set_buffer_size(&mut self, width: u32, height: u32) {
490        self.surface_mut().buffer_size = (width, height);
491    }
492
493    /// The physical size of this surface's framebuffer.
494    pub fn buffer_size(&self) -> (u32, u32) {
495        self.surface().buffer_size
496    }
497
498    /// Marks this surface as needing a redraw.
499    pub fn mark_dirty(&mut self) {
500        self.surface_mut().is_dirty = true;
501    }
502
503    /// Whether this surface owes the display a frame: stale pixels, or a
504    /// renderer that has not warmed its swapchain yet. See
505    /// [`AppShell::needs_redraw`].
506    pub fn needs_redraw(&self) -> bool {
507        let app_context = Rc::clone(&self.shell_app_ref().app_context);
508        app_context.enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
509    }
510
511    /// Whether a primary-button gesture that started on this surface is
512    /// still in progress.
513    pub fn has_active_pointer_gesture(&self) -> bool {
514        self.surface().has_active_pointer_gesture()
515    }
516
517    /// What the update and frame produced for this surface the last time
518    /// the app updated.
519    pub fn last_update_result(&self) -> FrameUpdateResult {
520        self.surface().last_update
521    }
522
523    /// Whether an update since the platform last presented this surface
524    /// changed its pixels. An update runs for the whole app, so the update a
525    /// platform ran for one window may have drawn another; this is how the
526    /// other window learns it has a frame to show.
527    pub fn frame_owed(&self) -> bool {
528        self.surface().frame_owed
529    }
530
531    /// [`Self::frame_owed`], cleared: the platform is about to present.
532    pub fn take_frame_owed(&mut self) -> bool {
533        std::mem::take(&mut self.surface_mut().frame_owed)
534    }
535
536    fn compute_frame_schedule(&self) -> FrameSchedule {
537        self.surface()
538            .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
539    }
540
541    /// The frame this surface asks its platform for, recorded for
542    /// [`Self::frame_scheduler_snapshot`].
543    pub fn frame_schedule(&self) -> FrameSchedule {
544        let schedule = self.compute_frame_schedule();
545        self.surface().frame_scheduler.record(schedule);
546        schedule
547    }
548
549    /// Computes this surface's frame schedule and applies it to `driver`.
550    pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
551    where
552        D: PlatformFrameDriver + ?Sized,
553    {
554        let schedule = self.compute_frame_schedule();
555        self.surface().frame_scheduler.schedule(schedule, driver);
556        schedule
557    }
558
559    /// The schedule this surface last recorded.
560    pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
561        self.surface().frame_scheduler.snapshot()
562    }
563
564    /// Sets how the platform should vote the display's frame rate for the
565    /// window showing this surface. See [`AppShell::set_frame_rate_preference`].
566    pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
567        self.surface_mut().frame_rate_preference = preference;
568    }
569
570    /// This surface's display frame-rate preference.
571    pub fn frame_rate_preference(&self) -> FrameRatePreference {
572        self.surface().frame_rate_preference
573    }
574
575    /// Where this surface's dev overlay draws the control for `mode`, in
576    /// logical pixels. See [`AppShell::dev_overlay_control_center`].
577    pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
578        self.surface().dev_overlay_control_center(mode)
579    }
580
581    pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
582        if !self.shell_app_ref().dev_options.frame_pacing_controls {
583            return false;
584        }
585        let Some(mode) = self
586            .surface()
587            .dev_overlay_controls
588            .iter()
589            .find(|control| control.bounds.contains(x, y))
590            .map(|control| control.mode)
591        else {
592            return false;
593        };
594        self.shell().set_frame_pacing_mode(mode);
595        true
596    }
597
598    /// Runs `block` with this surface's layout snapshot, built on demand.
599    pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
600        let (app, surface) = self.parts();
601        let app_context = Rc::clone(&app.app_context);
602        app_context.enter(|| block(surface.layout_tree_in_context(app)))
603    }
604
605    /// Runs `block` with this surface's semantics snapshot, built on demand;
606    /// `None` while semantics are disabled.
607    pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
608        let (app, surface) = self.parts();
609        let app_context = Rc::clone(&app.app_context);
610        app_context.enter(|| block(surface.semantics_tree_in_context(app)))
611    }
612
613    /// The pointer icon the platform has not applied to this surface's
614    /// window yet. See [`AppShell::take_pointer_icon_change`].
615    pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
616        self.surface().pointer_icon.take_change()
617    }
618
619    /// Offers this surface's pointer icon to the platform again. See
620    /// [`AppShell::refresh_pointer_icon`].
621    pub fn refresh_pointer_icon(&self) {
622        self.surface().pointer_icon.refresh()
623    }
624
625    /// Installs the platform text input for the window showing this
626    /// surface. Keyboard requests reach the handler of the surface the
627    /// platform last called active, and a hide reaches the handler that
628    /// showed. See [`AppShell::set_platform_text_input`].
629    pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
630        let id = self.id();
631        let app = self.shell_app();
632        app.text_input_routes.borrow_mut().set_handler(id, handler);
633        app.install_text_input_router();
634    }
635
636    /// Makes this the surface the platform considers focused: the one the
637    /// soft keyboard belongs to. Pointer presses do this on their own.
638    pub fn activate(&mut self) {
639        let id = self.id();
640        self.shell_app()
641            .text_input_routes
642            .borrow_mut()
643            .set_active(id);
644    }
645}