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::{MemoryApplier, NodeId, collections::map::HashSet};
16use cranpose_foundation::{PointerButton, PointerButtons, PointerSource, RotaryScrollEvent};
17use cranpose_render_common::Renderer;
18use cranpose_ui::{
19    LayoutNode, LayoutTree, MovedSceneNode, PlatformTextInputHandler, SemanticsTree,
20    pointer_icon_session::PointerIconState,
21};
22use cranpose_ui_graphics::{Point, PointerIcon, Size};
23use web_time::Instant;
24
25use crate::{
26    AppShell, DevOverlayControl, FramePacingMode, FrameRatePreference, FrameSchedule,
27    FrameScheduler, FrameUpdateResult, PlatformFrameDriver, ShellApp, SurfaceRoutingScratch,
28    hit_path_tracker::{HitPathTracker, PointerId},
29};
30
31/// Names a root surface of the app: the primary window, or a window root
32/// declared with [`Modifier::window_root`](cranpose_ui::Modifier::window_root)
33/// by the id given there.
34#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
35pub enum RootId {
36    /// The composition root, drawn in the app's first window.
37    Primary,
38    /// The node carrying `Modifier::window_root` with this id.
39    Window(u64),
40}
41
42/// The shell's bookkeeping for one root.
43///
44/// The primary surface draws the composition root. A window surface draws the
45/// window root node registered under its id, and draws nothing while that
46/// node is not in the tree. Everything here is what an OS owns per window:
47/// the framebuffer, the viewport, the pointer, the cursor image, the soft
48/// keyboard, the frame the window is asked for.
49pub struct RootSurface<R: Renderer> {
50    pub(crate) id: RootId,
51    pub(crate) root: Option<NodeId>,
52    pub(crate) renderer: R,
53    pub(crate) cursor: (f32, f32),
54    pub(crate) viewport: (f32, f32),
55    pub(crate) buffer_size: (u32, u32),
56    pub(crate) layout_tree: Option<LayoutTree>,
57    pub(crate) semantics_tree: Option<SemanticsTree>,
58    /// A layout pass ran since `semantics_tree` was brought up to date, so
59    /// its bounds may be out of date though no node marked its semantics.
60    semantics_moved: bool,
61    pub(crate) modal_focus: Vec<(NodeId, Option<NodeId>)>,
62    pub(crate) frame_rate_preference: FrameRatePreference,
63    pub(crate) scene_dirty: bool,
64    pub(crate) scoped_layout_scene_nodes: Vec<NodeId>,
65    /// Nodes the layout pass only moved: the scene moves their layers.
66    pub(crate) scoped_moved_scene_nodes: Vec<NodeId>,
67    pub(crate) scoped_draw_nodes: Vec<NodeId>,
68    pub(crate) scoped_layer_property_nodes: Vec<NodeId>,
69    pub(crate) structural_scene_nodes: Vec<NodeId>,
70    pub(crate) partial_scene_nodes: Vec<NodeId>,
71    /// The nodes the retained redraw walk has yet to visit, empty between
72    /// frames; kept for its storage.
73    pub(crate) redraw_walk: Vec<NodeId>,
74    pub(crate) retained_visual_nodes: HashSet<NodeId>,
75    pub(crate) is_dirty: bool,
76    pub(crate) buttons_pressed: PointerButtons,
77    pub(crate) pointer_source: PointerSource,
78    pub(crate) hit_path_tracker: HitPathTracker,
79    pub(crate) hovered_nodes: Vec<NodeId>,
80    pub(crate) on_rotary_scroll: Option<Rc<dyn Fn(RotaryScrollEvent) -> bool>>,
81    pub(crate) dev_overlay_controls: Vec<DevOverlayControl>,
82    pub(crate) inspector: crate::inspector::DeveloperInspector,
83    pub(crate) dev_overlay_text: String,
84    pub(crate) dev_overlay_last_refresh: Option<Instant>,
85    pub(crate) dev_overlay_viewport: Option<Size>,
86    pub(crate) frame_scheduler: FrameScheduler,
87    pub(crate) pointer_icon: PointerIconState,
88    pub(crate) last_update: FrameUpdateResult,
89    pub(crate) frame_owed: bool,
90    pub(crate) screen_origin: Option<Point>,
91}
92
93impl<R: Renderer> RootSurface<R> {
94    pub(crate) fn new(
95        id: RootId,
96        renderer: R,
97        buffer_size: (u32, u32),
98        viewport: (f32, f32),
99    ) -> Self {
100        Self {
101            id,
102            root: None,
103            renderer,
104            cursor: (0.0, 0.0),
105            viewport,
106            buffer_size,
107            layout_tree: None,
108            semantics_tree: None,
109            semantics_moved: false,
110            modal_focus: Vec::new(),
111            frame_rate_preference: FrameRatePreference::default(),
112            scene_dirty: true,
113            scoped_layout_scene_nodes: Vec::new(),
114            scoped_moved_scene_nodes: Vec::new(),
115            scoped_draw_nodes: Vec::new(),
116            scoped_layer_property_nodes: Vec::new(),
117            structural_scene_nodes: Vec::new(),
118            partial_scene_nodes: Vec::new(),
119            redraw_walk: Vec::new(),
120            retained_visual_nodes: HashSet::default(),
121            is_dirty: true,
122            buttons_pressed: PointerButtons::NONE,
123            pointer_source: PointerSource::Unknown,
124            hit_path_tracker: HitPathTracker::new(),
125            hovered_nodes: Vec::new(),
126            on_rotary_scroll: None,
127            dev_overlay_controls: Vec::new(),
128            inspector: crate::inspector::DeveloperInspector::default(),
129            dev_overlay_text: String::new(),
130            dev_overlay_last_refresh: None,
131            dev_overlay_viewport: None,
132            frame_scheduler: FrameScheduler::default(),
133            pointer_icon: PointerIconState::new(),
134            last_update: FrameUpdateResult::default(),
135            frame_owed: false,
136            screen_origin: None,
137        }
138    }
139
140    pub(crate) fn root_node(&self, app: &ShellApp) -> Option<NodeId> {
141        match self.id {
142            RootId::Primary => app.composition.root(),
143            RootId::Window(_) => self.root,
144        }
145    }
146
147    pub(crate) fn owns_nodes_under(&self, window_root: Option<NodeId>) -> bool {
148        match self.id {
149            RootId::Primary => window_root.is_none(),
150            RootId::Window(_) => self.root.is_some() && self.root == window_root,
151        }
152    }
153
154    pub(crate) fn viewport_size(&self) -> Size {
155        Size {
156            width: self.viewport.0,
157            height: self.viewport.1,
158        }
159    }
160
161    pub(crate) fn screen_point_inside(&self, screen: Point) -> Option<Point> {
162        let origin = self.screen_origin?;
163        let local = Point {
164            x: screen.x - origin.x,
165            y: screen.y - origin.y,
166        };
167        (local.x >= 0.0
168            && local.y >= 0.0
169            && local.x <= self.viewport.0
170            && local.y <= self.viewport.1)
171            .then_some(local)
172    }
173
174    pub(crate) fn set_root(&mut self, root: Option<NodeId>) {
175        if self.root == root {
176            return;
177        }
178        self.root = root;
179        self.forget_snapshots();
180        self.invalidate_scene_root(root);
181        self.retained_visual_nodes.clear();
182        self.hit_path_tracker.clear();
183        self.hovered_nodes.clear();
184        self.buttons_pressed = PointerButtons::NONE;
185        self.is_dirty = true;
186    }
187
188    pub(crate) fn invalidate_scene_root(&mut self, root: Option<NodeId>) {
189        self.scoped_layout_scene_nodes.clear();
190        self.scoped_layout_scene_nodes.extend(root);
191        self.scoped_moved_scene_nodes.clear();
192        self.scoped_draw_nodes.clear();
193        self.scoped_layer_property_nodes.clear();
194        self.structural_scene_nodes.clear();
195        self.partial_scene_nodes.clear();
196        self.scene_dirty = true;
197    }
198
199    /// Drops the layout snapshot and marks the semantics tree for an update,
200    /// which keeps what did not change.
201    pub(crate) fn forget_snapshots(&mut self) {
202        self.layout_tree = None;
203        self.semantics_moved = true;
204    }
205
206    pub(crate) fn has_active_pointer_gesture(&self) -> bool {
207        self.buttons_pressed != PointerButtons::NONE
208            && self.hit_path_tracker.has_path(PointerId::PRIMARY)
209    }
210
211    pub(crate) fn has_active_primary_gesture(&self) -> bool {
212        self.buttons_pressed.contains(PointerButton::Primary)
213            && self.hit_path_tracker.has_path(PointerId::PRIMARY)
214    }
215
216    pub(crate) fn renderer_warmup_due(&self, app: &ShellApp) -> bool {
217        self.renderer.needs_frame_warmup() && !app.composition.runtime().has_frame_callbacks()
218    }
219
220    pub(crate) fn needs_redraw_in_context(&self, app: &ShellApp) -> bool {
221        app.has_stale_work_in_context()
222            || app.composition.should_recompose()
223            || self.is_dirty
224            || self.scene_dirty
225            || self.renderer_warmup_due(app)
226    }
227
228    pub(crate) fn compute_frame_schedule(
229        &self,
230        app: &ShellApp,
231        surfaces_dirty: bool,
232    ) -> FrameSchedule {
233        let (needs_update, needs_frame) = app.app_context.enter(|| {
234            let needs_frame = self.is_dirty
235                || self.scene_dirty
236                || app.wants_frame_in_context()
237                || self.has_active_pointer_gesture()
238                || self.renderer_warmup_due(app);
239            (app.needs_ui_update_in_context(surfaces_dirty), needs_frame)
240        });
241        FrameSchedule {
242            needs_update,
243            needs_frame,
244            next_deadline: app.next_event_time(),
245        }
246    }
247
248    pub(crate) fn invalidate_dev_overlay_text(&mut self) {
249        self.dev_overlay_text.clear();
250        self.dev_overlay_last_refresh = None;
251        self.dev_overlay_viewport = None;
252    }
253
254    pub(crate) fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
255        self.dev_overlay_controls
256            .iter()
257            .find(|control| control.mode == mode)
258            .map(|control| {
259                (
260                    control.bounds.x + control.bounds.width * 0.5,
261                    control.bounds.y + control.bounds.height * 0.5,
262                )
263            })
264    }
265
266    pub(crate) fn layout_tree_in_context(&mut self, app: &mut ShellApp) -> Option<&LayoutTree> {
267        if self.layout_tree.is_none() {
268            let root = self.root_node(app)?;
269            let mut applier = app.composition.applier_mut();
270            match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
271                Ok(layout_tree) => {
272                    self.layout_tree = layout_tree;
273                }
274                Err(err) => {
275                    log::debug!("failed to build layout snapshot: {err}");
276                    return None;
277                }
278            }
279        }
280        self.layout_tree.as_ref()
281    }
282
283    /// The node an open modal takes this surface over with, the root its
284    /// semantics tree would have, found without building the tree.
285    pub(crate) fn top_modal(&self, app: &mut ShellApp) -> Option<NodeId> {
286        let root = self.root_node(app)?;
287        let mut applier = app.composition.applier_mut();
288        cranpose_ui::top_modal_from_applier(&mut applier, root).unwrap_or_else(|err| {
289            log::debug!("failed to find the top modal under root #{root}: {err}");
290            None
291        })
292    }
293
294    pub(crate) fn semantics_tree_in_context(
295        &mut self,
296        app: &mut ShellApp,
297    ) -> Option<&SemanticsTree> {
298        if !app.semantics_enabled && !self.inspector.state.open {
299            return None;
300        }
301        self.semantics_tree_for_input(app)
302    }
303
304    pub(crate) fn semantics_tree_for_input(
305        &mut self,
306        app: &mut ShellApp,
307    ) -> Option<&SemanticsTree> {
308        app.flush_semantics_invalidations();
309        let root = self.root_node(app)?;
310        let semantics_dirty = {
311            let mut applier = app.composition.applier_mut();
312            cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
313                log::debug!("failed to check semantics dirty status for root #{root}: {err}");
314                true
315            })
316        };
317        if self.semantics_tree.is_none() || self.semantics_moved || semantics_dirty {
318            let mut applier = app.composition.applier_mut();
319            match cranpose_ui::update_semantics_tree_from_applier(
320                &mut applier,
321                root,
322                &mut self.semantics_tree,
323            ) {
324                Ok(()) => {
325                    #[cfg(debug_assertions)]
326                    crate::semantics_check::assert_matches_rebuild(
327                        &mut applier,
328                        root,
329                        self.semantics_tree.as_ref(),
330                    );
331                    self.semantics_moved = false;
332                    app.semantics_snapshot_revision =
333                        app.semantics_snapshot_revision.wrapping_add(1);
334                }
335                Err(err) => {
336                    log::debug!("failed to build semantics snapshot: {err}");
337                    return None;
338                }
339            }
340        }
341        self.semantics_tree.as_ref()
342    }
343}
344
345#[derive(Default)]
346pub(crate) struct TextInputRoutes {
347    active: Option<RootId>,
348    shown: Option<RootId>,
349    handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
350}
351
352impl TextInputRoutes {
353    pub(crate) fn active(&self) -> RootId {
354        self.active.unwrap_or(RootId::Primary)
355    }
356
357    pub(crate) fn set_active(&mut self, root: RootId) {
358        self.active = Some(root);
359    }
360
361    pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
362        self.remove(root);
363        self.handlers.push((root, handler));
364    }
365
366    pub(crate) fn remove(&mut self, root: RootId) {
367        self.handlers.retain(|(id, _)| *id != root);
368        if self.shown == Some(root) {
369            self.shown = None;
370        }
371    }
372
373    fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
374        self.handlers
375            .iter()
376            .find(|(id, _)| *id == root)
377            .map(|(_, handler)| Rc::clone(handler))
378    }
379
380    fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
381        let active = self.active();
382        let handler = self.handler(active);
383        if handler.is_some() {
384            self.shown = Some(active);
385        }
386        handler
387    }
388
389    fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
390        let target = self.shown.take().unwrap_or_else(|| self.active());
391        self.handler(target)
392    }
393}
394
395pub(crate) struct TextInputRouter {
396    pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
397}
398
399impl PlatformTextInputHandler for TextInputRouter {
400    fn show_keyboard(&self) {
401        let handler = self.routes.borrow_mut().take_show_target();
402        if let Some(handler) = handler {
403            handler.show_keyboard();
404        }
405    }
406
407    fn hide_keyboard(&self) {
408        let handler = self.routes.borrow_mut().take_hide_target();
409        if let Some(handler) = handler {
410            handler.hide_keyboard();
411        }
412    }
413}
414
415#[derive(Clone, Copy)]
416pub(crate) enum SurfaceDirtyLane {
417    Layout,
418    Moved,
419    Draw,
420    LayerProperties,
421    Structural,
422}
423
424fn append_surface_dirty_node(
425    surface: &mut RootSurface<impl Renderer>,
426    surface_index: usize,
427    node: NodeId,
428    lane: SurfaceDirtyLane,
429    seen: &mut HashSet<(usize, NodeId)>,
430) {
431    // The scene update drops repeats of reshaped and moved nodes: the
432    // reshaped ones go into a sorted partial list, the moved ones into its
433    // dirty map. Both lists are taken by the frame that sees them.
434    match lane {
435        SurfaceDirtyLane::Layout => {
436            surface.scene_dirty = true;
437            surface.scoped_layout_scene_nodes.push(node);
438        }
439        SurfaceDirtyLane::Moved => {
440            surface.scene_dirty = true;
441            surface.scoped_moved_scene_nodes.push(node);
442        }
443        SurfaceDirtyLane::Draw => surface.scoped_draw_nodes.push(node),
444        SurfaceDirtyLane::LayerProperties => surface.scoped_layer_property_nodes.push(node),
445        SurfaceDirtyLane::Structural => {
446            if seen.insert((surface_index, node)) {
447                surface.structural_scene_nodes.push(node);
448            }
449        }
450    }
451}
452
453pub(crate) fn route_nodes_by_surface<R: Renderer>(
454    app: &mut ShellApp,
455    surfaces: &mut [RootSurface<R>],
456    nodes: impl IntoIterator<Item = NodeId>,
457    lane: SurfaceDirtyLane,
458    scratch: &mut SurfaceRoutingScratch,
459) {
460    let Some(primary_root) = app.composition.root() else {
461        scratch.attached.clear();
462        scratch.owners.clear();
463        return;
464    };
465    let mut applier = app.composition.applier_mut();
466    applier.scene_nodes_attached_to_into(
467        nodes,
468        primary_root,
469        &mut scratch.attached,
470        &mut scratch.attachment,
471    );
472    if scratch.attached.is_empty() {
473        scratch.owners.clear();
474        return;
475    }
476    scratch.seen.clear();
477    if app.app_context.window_roots().is_empty() {
478        scratch.owners.clear();
479        if let Some(index) = surfaces
480            .iter()
481            .position(|surface| surface.id == RootId::Primary)
482        {
483            let surface = &mut surfaces[index];
484            for node in scratch.attached.iter().flatten().copied() {
485                append_surface_dirty_node(surface, index, node, lane, &mut scratch.seen);
486            }
487        }
488        return;
489    }
490    cranpose_ui::nearest_window_roots_into(
491        &mut applier,
492        scratch.attached.iter().flatten().copied(),
493        &mut scratch.owners,
494        &mut scratch.window_roots,
495    );
496    for (node, owner) in scratch.attached.iter().flatten().zip(&scratch.owners) {
497        let node = *node;
498        if let Some(index) = surfaces
499            .iter()
500            .position(|surface| surface.owns_nodes_under(*owner))
501        {
502            let surface = &mut surfaces[index];
503            append_surface_dirty_node(surface, index, node, lane, &mut scratch.seen);
504        }
505    }
506}
507
508/// Routes the nodes a layout pass laid out again, `laid_out`, and the nodes
509/// it only moved. A moved node is left out when the scene update of its
510/// surface lays out its placer again: that update places every child of the
511/// placer's layer anew, so the node's own record would be found and settled
512/// there and nowhere else.
513pub(crate) fn route_geometry_scene_nodes<R: Renderer>(
514    app: &mut ShellApp,
515    surfaces: &mut [RootSurface<R>],
516    laid_out: impl IntoIterator<Item = NodeId>,
517    moved: &[MovedSceneNode],
518    scratch: &mut SurfaceRoutingScratch,
519) {
520    if moved.is_empty() {
521        route_nodes_by_surface(app, surfaces, laid_out, SurfaceDirtyLane::Layout, scratch);
522        return;
523    }
524    scratch.laid_out_starts.clear();
525    scratch.laid_out_starts.extend(
526        surfaces
527            .iter()
528            .map(|surface| surface.scoped_layout_scene_nodes.len()),
529    );
530    route_nodes_by_surface(app, surfaces, laid_out, SurfaceDirtyLane::Layout, scratch);
531    let mut routed = std::mem::take(&mut scratch.moved);
532    routed.clear();
533    {
534        let applier = app.composition.applier_mut();
535        scratch.laid_out.reset(&applier);
536        for (surface, &start) in surfaces.iter().zip(&scratch.laid_out_starts) {
537            for &node in &surface.scoped_layout_scene_nodes[start..] {
538                scratch.laid_out.set(&applier, node, 1);
539            }
540        }
541        let marks = &scratch.laid_out;
542        let window_roots = !app.app_context.window_roots().is_empty();
543        // The children one node placed come together: their placer is
544        // looked up once.
545        let mut last_placer = None;
546        for &moved in moved {
547            let placed_again = moved.placer.is_some_and(|placer| match last_placer {
548                Some((last, laid_out)) if last == placer => laid_out,
549                _ => {
550                    let laid_out = marks.get(&applier, placer) != 0;
551                    last_placer = Some((placer, laid_out));
552                    laid_out
553                }
554            });
555            if !placed_again || window_roots && is_window_root(&applier, moved.node) {
556                routed.push(moved.node);
557            }
558        }
559    }
560    route_nodes_by_surface(
561        app,
562        surfaces,
563        routed.iter().copied(),
564        SurfaceDirtyLane::Moved,
565        scratch,
566    );
567    scratch.moved = routed;
568}
569
570/// Whether `node` is a window root, which goes to a surface of its own.
571fn is_window_root(applier: &MemoryApplier, node: NodeId) -> bool {
572    applier.get_ref(node).is_ok_and(|node| {
573        let node: &dyn std::any::Any = node;
574        node.downcast_ref::<LayoutNode>()
575            .is_some_and(LayoutNode::is_window_root)
576    })
577}
578
579/// One surface borrowed together with its shell: the handle a platform
580/// delivers a window's events through and reads a window's frame from.
581///
582/// [`AppShell::surface`] hands one out per root. Every method of [`AppShell`]
583/// that names no root acts on the primary surface through the same code, so
584/// a single-window platform never sees this type.
585pub struct SurfaceMut<'a, R: Renderer> {
586    pub(crate) shell: &'a mut AppShell<R>,
587    pub(crate) index: usize,
588}
589
590impl<'a, R> SurfaceMut<'a, R>
591where
592    R: Renderer,
593    R::Error: Debug,
594{
595    pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
596        Self { shell, index }
597    }
598
599    /// The whole app, for what a window's event needs beyond its surface:
600    /// the clipboard, the dev options, a debug report.
601    pub fn shell(&mut self) -> &mut AppShell<R> {
602        self.shell
603    }
604
605    pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
606        &mut self.shell.app
607    }
608
609    pub(crate) fn shell_app_ref(&self) -> &ShellApp {
610        &self.shell.app
611    }
612
613    pub(crate) fn surface(&self) -> &RootSurface<R> {
614        &self.shell.surfaces[self.index]
615    }
616
617    pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
618        &mut self.shell.surfaces[self.index]
619    }
620
621    pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
622        let shell = &mut *self.shell;
623        (&mut shell.app, &mut shell.surfaces[self.index])
624    }
625
626    /// Which root this surface draws.
627    pub fn id(&self) -> RootId {
628        self.surface().id
629    }
630
631    /// The node this surface draws from, when it has one.
632    pub fn root(&self) -> Option<NodeId> {
633        let surface = self.surface();
634        surface.root_node(self.shell_app_ref())
635    }
636
637    /// The renderer that draws this surface.
638    pub fn renderer(&mut self) -> &mut R {
639        &mut self.surface_mut().renderer
640    }
641
642    /// Runs a presentation operation with this surface's app context active.
643    /// Software text rasterization uses the context's font measurement services.
644    pub fn with_renderer<T>(&mut self, block: impl FnOnce(&mut R) -> T) -> T {
645        let (app, surface) = self.parts();
646        app.app_context.enter(|| block(&mut surface.renderer))
647    }
648
649    /// The scene this surface last built.
650    pub fn scene(&self) -> &R::Scene {
651        self.surface().renderer.scene()
652    }
653
654    /// Sets the logical size this surface lays out and draws into.
655    ///
656    /// The primary surface's viewport is the composition root's constraints.
657    /// A window surface's viewport is what its renderer draws into; the
658    /// window root lays out to the size its descriptor reports, which the
659    /// platform keeps equal to this. The next update lays out and renders;
660    /// [`AppShell::set_viewport`] additionally runs that frame at once.
661    pub fn set_viewport(&mut self, width: f32, height: f32) {
662        self.surface_mut().viewport = (width, height);
663        match self.id() {
664            RootId::Primary => self.shell_app().request_forced_layout_pass(),
665            RootId::Window(_) => {
666                if let Some(root) = self.root() {
667                    self.shell_app_ref()
668                        .app_context
669                        .enter(|| cranpose_ui::schedule_measure_repass(root));
670                }
671                self.shell_app().request_layout_pass();
672            }
673        }
674        let root = self.root();
675        self.surface_mut().invalidate_scene_root(root);
676        self.mark_dirty();
677    }
678
679    /// Tells the shell where the window drawing this surface sits on the
680    /// screen, in logical pixels, so pointer events can carry a
681    /// [`screen_position`](cranpose_foundation::PointerEvent::screen_position).
682    /// A platform sets it when the window moves and before it delivers a
683    /// pointer sample; `None` says the platform does not know.
684    pub fn set_screen_origin(&mut self, origin: Option<Point>) {
685        self.surface_mut().screen_origin = origin;
686    }
687
688    /// Where the window drawing this surface sits on the screen, as the
689    /// platform last said.
690    pub fn screen_origin(&self) -> Option<Point> {
691        self.surface().screen_origin
692    }
693
694    /// The logical size this surface draws into.
695    pub fn viewport_size(&self) -> (f32, f32) {
696        self.surface().viewport
697    }
698
699    /// Sets the physical size of this surface's framebuffer.
700    pub fn set_buffer_size(&mut self, width: u32, height: u32) {
701        self.surface_mut().buffer_size = (width, height);
702    }
703
704    /// The physical size of this surface's framebuffer.
705    pub fn buffer_size(&self) -> (u32, u32) {
706        self.surface().buffer_size
707    }
708
709    /// Marks this surface as needing a redraw.
710    pub fn mark_dirty(&mut self) {
711        self.surface_mut().is_dirty = true;
712    }
713
714    /// Whether this surface owes the display a frame: stale pixels, or a
715    /// renderer that has not warmed its swapchain yet. See
716    /// [`AppShell::needs_redraw`].
717    pub fn needs_redraw(&self) -> bool {
718        self.shell_app_ref()
719            .app_context
720            .enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
721    }
722
723    /// Whether a pointer button is held down on a control of this surface,
724    /// whichever button it is.
725    pub fn has_active_pointer_gesture(&self) -> bool {
726        self.surface().has_active_pointer_gesture()
727    }
728
729    /// Whether the primary button is held down on a control of this
730    /// surface. A platform that polls the mouse's primary button compares
731    /// it with this, so a held right button does not look like a lost left
732    /// release.
733    pub fn has_active_primary_gesture(&self) -> bool {
734        self.surface().has_active_primary_gesture()
735    }
736
737    /// What the update and frame produced for this surface the last time
738    /// the app updated.
739    pub fn last_update_result(&self) -> FrameUpdateResult {
740        self.surface().last_update
741    }
742
743    /// Whether an update since the platform last presented this surface
744    /// changed its pixels. An update runs for the whole app, so the update a
745    /// platform ran for one window may have drawn another; this is how the
746    /// other window learns it has a frame to show.
747    pub fn frame_owed(&self) -> bool {
748        self.surface().frame_owed
749    }
750
751    /// [`Self::frame_owed`], cleared: the platform is about to present.
752    pub fn take_frame_owed(&mut self) -> bool {
753        std::mem::take(&mut self.surface_mut().frame_owed)
754    }
755
756    fn compute_frame_schedule(&self) -> FrameSchedule {
757        self.surface()
758            .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
759    }
760
761    /// The frame this surface asks its platform for, recorded for
762    /// [`Self::frame_scheduler_snapshot`].
763    pub fn frame_schedule(&self) -> FrameSchedule {
764        let schedule = self.compute_frame_schedule();
765        self.surface().frame_scheduler.record(schedule);
766        schedule
767    }
768
769    /// Computes this surface's frame schedule and applies it to `driver`.
770    pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
771    where
772        D: PlatformFrameDriver + ?Sized,
773    {
774        let schedule = self.compute_frame_schedule();
775        self.surface().frame_scheduler.schedule(schedule, driver);
776        schedule
777    }
778
779    /// The schedule this surface last recorded.
780    pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
781        self.surface().frame_scheduler.snapshot()
782    }
783
784    /// Sets how the platform should vote the display's frame rate for the
785    /// window showing this surface. See [`AppShell::set_frame_rate_preference`].
786    pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
787        self.surface_mut().frame_rate_preference = preference;
788    }
789
790    /// This surface's display frame-rate preference.
791    pub fn frame_rate_preference(&self) -> FrameRatePreference {
792        self.surface().frame_rate_preference
793    }
794
795    /// Where this surface's dev overlay draws the control for `mode`, in
796    /// logical pixels. See [`AppShell::dev_overlay_control_center`].
797    pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
798        self.surface().dev_overlay_control_center(mode)
799    }
800
801    pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
802        if !self.shell_app_ref().dev_options.frame_pacing_controls {
803            return false;
804        }
805        let Some(mode) = self
806            .surface()
807            .dev_overlay_controls
808            .iter()
809            .find(|control| control.bounds.contains(x, y))
810            .map(|control| control.mode)
811        else {
812            return false;
813        };
814        self.shell().set_frame_pacing_mode(mode);
815        true
816    }
817
818    /// Runs `block` with this surface's layout snapshot, built on demand.
819    pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
820        let (app, surface) = self.parts();
821        let _app_context = app.app_context.enter_scope();
822        block(surface.layout_tree_in_context(app))
823    }
824
825    /// Runs `block` with this surface's semantics snapshot, built on demand;
826    /// `None` while semantics are disabled.
827    pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
828        let (app, surface) = self.parts();
829        let _app_context = app.app_context.enter_scope();
830        block(surface.semantics_tree_in_context(app))
831    }
832
833    /// The pointer icon the platform has not applied to this surface's
834    /// window yet. See [`AppShell::take_pointer_icon_change`].
835    pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
836        self.surface().pointer_icon.take_change()
837    }
838
839    /// Offers this surface's pointer icon to the platform again. See
840    /// [`AppShell::refresh_pointer_icon`].
841    pub fn refresh_pointer_icon(&self) {
842        self.surface().pointer_icon.refresh();
843    }
844
845    /// Installs the platform text input for the window showing this
846    /// surface. Keyboard requests reach the handler of the surface the
847    /// platform last called active, and a hide reaches the handler that
848    /// showed. See [`AppShell::set_platform_text_input`].
849    pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
850        let id = self.id();
851        let app = self.shell_app();
852        app.text_input_routes.borrow_mut().set_handler(id, handler);
853        app.install_text_input_router();
854    }
855
856    /// Makes this the surface the platform considers focused: the one the
857    /// soft keyboard belongs to. Pointer presses do this on their own.
858    pub fn activate(&mut self) {
859        let id = self.id();
860        self.shell_app()
861            .text_input_routes
862            .borrow_mut()
863            .set_active(id);
864    }
865}