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