1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
34pub enum RootId {
35 Primary,
37 Window(u64),
39}
40
41pub 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 semantics_moved: bool,
60 pub(crate) modal_focus: Vec<(NodeId, Option<NodeId>)>,
61 pub(crate) frame_rate_preference: FrameRatePreference,
62 pub(crate) scene_dirty: bool,
63 pub(crate) scoped_layout_scene_nodes: Vec<NodeId>,
64 pub(crate) retained_visual_nodes: HashSet<NodeId>,
65 pub(crate) is_dirty: bool,
66 pub(crate) buttons_pressed: PointerButtons,
67 pub(crate) pointer_source: PointerSource,
68 pub(crate) hit_path_tracker: HitPathTracker,
69 pub(crate) hovered_nodes: Vec<NodeId>,
70 pub(crate) on_rotary_scroll: Option<Rc<dyn Fn(RotaryScrollEvent) -> bool>>,
71 pub(crate) dev_overlay_controls: Vec<DevOverlayControl>,
72 pub(crate) inspector: crate::inspector::DeveloperInspector,
73 pub(crate) dev_overlay_text: String,
74 pub(crate) dev_overlay_last_refresh: Option<Instant>,
75 pub(crate) dev_overlay_viewport: Option<Size>,
76 pub(crate) frame_scheduler: FrameScheduler,
77 pub(crate) pointer_icon: PointerIconState,
78 pub(crate) last_update: FrameUpdateResult,
79 pub(crate) frame_owed: bool,
80 pub(crate) screen_origin: Option<Point>,
81}
82
83impl<R: Renderer> RootSurface<R> {
84 pub(crate) fn new(
85 id: RootId,
86 renderer: R,
87 buffer_size: (u32, u32),
88 viewport: (f32, f32),
89 ) -> Self {
90 Self {
91 id,
92 root: None,
93 renderer,
94 cursor: (0.0, 0.0),
95 viewport,
96 buffer_size,
97 layout_tree: None,
98 semantics_tree: None,
99 semantics_moved: false,
100 modal_focus: Vec::new(),
101 frame_rate_preference: FrameRatePreference::default(),
102 scene_dirty: true,
103 scoped_layout_scene_nodes: Vec::new(),
104 retained_visual_nodes: HashSet::default(),
105 is_dirty: true,
106 buttons_pressed: PointerButtons::NONE,
107 pointer_source: PointerSource::Unknown,
108 hit_path_tracker: HitPathTracker::new(),
109 hovered_nodes: Vec::new(),
110 on_rotary_scroll: None,
111 dev_overlay_controls: Vec::new(),
112 inspector: crate::inspector::DeveloperInspector::default(),
113 dev_overlay_text: String::new(),
114 dev_overlay_last_refresh: None,
115 dev_overlay_viewport: None,
116 frame_scheduler: FrameScheduler::default(),
117 pointer_icon: PointerIconState::new(),
118 last_update: FrameUpdateResult::default(),
119 frame_owed: false,
120 screen_origin: None,
121 }
122 }
123
124 pub(crate) fn root_node(&self, app: &ShellApp) -> Option<NodeId> {
125 match self.id {
126 RootId::Primary => app.composition.root(),
127 RootId::Window(_) => self.root,
128 }
129 }
130
131 pub(crate) fn owns_nodes_under(&self, window_root: Option<NodeId>) -> bool {
132 match self.id {
133 RootId::Primary => window_root.is_none(),
134 RootId::Window(_) => self.root.is_some() && self.root == window_root,
135 }
136 }
137
138 pub(crate) fn viewport_size(&self) -> Size {
139 Size {
140 width: self.viewport.0,
141 height: self.viewport.1,
142 }
143 }
144
145 pub(crate) fn screen_point_inside(&self, screen: Point) -> Option<Point> {
146 let origin = self.screen_origin?;
147 let local = Point {
148 x: screen.x - origin.x,
149 y: screen.y - origin.y,
150 };
151 (local.x >= 0.0
152 && local.y >= 0.0
153 && local.x <= self.viewport.0
154 && local.y <= self.viewport.1)
155 .then_some(local)
156 }
157
158 pub(crate) fn set_root(&mut self, root: Option<NodeId>) {
159 if self.root == root {
160 return;
161 }
162 self.root = root;
163 self.forget_snapshots();
164 self.scoped_layout_scene_nodes.clear();
165 self.retained_visual_nodes.clear();
166 self.hit_path_tracker.clear();
167 self.hovered_nodes.clear();
168 self.buttons_pressed = PointerButtons::NONE;
169 self.scene_dirty = true;
170 self.is_dirty = true;
171 }
172
173 pub(crate) fn forget_snapshots(&mut self) {
176 self.layout_tree = None;
177 self.semantics_moved = true;
178 }
179
180 pub(crate) fn has_active_pointer_gesture(&self) -> bool {
181 self.buttons_pressed != PointerButtons::NONE
182 && self.hit_path_tracker.has_path(PointerId::PRIMARY)
183 }
184
185 pub(crate) fn renderer_warmup_due(&self, app: &ShellApp) -> bool {
186 self.renderer.needs_frame_warmup() && !app.runtime.runtime_handle().has_frame_callbacks()
187 }
188
189 pub(crate) fn needs_redraw_in_context(&self, app: &ShellApp) -> bool {
190 app.has_stale_work_in_context()
191 || app.composition.should_recompose()
192 || self.is_dirty
193 || self.scene_dirty
194 || self.renderer_warmup_due(app)
195 }
196
197 pub(crate) fn compute_frame_schedule(
198 &self,
199 app: &ShellApp,
200 surfaces_dirty: bool,
201 ) -> FrameSchedule {
202 let app_context = Rc::clone(&app.app_context);
203 let (needs_update, needs_frame) = app_context.enter(|| {
204 let needs_frame = self.is_dirty
205 || self.scene_dirty
206 || app.wants_frame_in_context()
207 || self.has_active_pointer_gesture()
208 || self.renderer_warmup_due(app);
209 (app.needs_ui_update_in_context(surfaces_dirty), needs_frame)
210 });
211 FrameSchedule {
212 needs_update,
213 needs_frame,
214 next_deadline: app.next_event_time(),
215 }
216 }
217
218 pub(crate) fn invalidate_dev_overlay_text(&mut self) {
219 self.dev_overlay_text.clear();
220 self.dev_overlay_last_refresh = None;
221 self.dev_overlay_viewport = None;
222 }
223
224 pub(crate) fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
225 self.dev_overlay_controls
226 .iter()
227 .find(|control| control.mode == mode)
228 .map(|control| {
229 (
230 control.bounds.x + control.bounds.width * 0.5,
231 control.bounds.y + control.bounds.height * 0.5,
232 )
233 })
234 }
235
236 pub(crate) fn layout_tree_in_context(&mut self, app: &mut ShellApp) -> Option<&LayoutTree> {
237 if self.layout_tree.is_none() {
238 let root = self.root_node(app)?;
239 let mut applier = app.composition.applier_mut();
240 match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
241 Ok(layout_tree) => {
242 self.layout_tree = layout_tree;
243 }
244 Err(err) => {
245 log::debug!("failed to build layout snapshot: {err}");
246 return None;
247 }
248 }
249 }
250 self.layout_tree.as_ref()
251 }
252
253 pub(crate) fn top_modal(&self, app: &mut ShellApp) -> Option<NodeId> {
256 let root = self.root_node(app)?;
257 let mut applier = app.composition.applier_mut();
258 cranpose_ui::top_modal_from_applier(&mut applier, root).unwrap_or_else(|err| {
259 log::debug!("failed to find the top modal under root #{root}: {err}");
260 None
261 })
262 }
263
264 pub(crate) fn semantics_tree_in_context(
265 &mut self,
266 app: &mut ShellApp,
267 ) -> Option<&SemanticsTree> {
268 if !app.semantics_enabled && !self.inspector.state.open {
269 return None;
270 }
271 self.semantics_tree_for_input(app)
272 }
273
274 pub(crate) fn semantics_tree_for_input(
275 &mut self,
276 app: &mut ShellApp,
277 ) -> Option<&SemanticsTree> {
278 app.flush_semantics_invalidations();
279 let root = self.root_node(app)?;
280 let semantics_dirty = {
281 let mut applier = app.composition.applier_mut();
282 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
283 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
284 true
285 })
286 };
287 if self.semantics_tree.is_none() || self.semantics_moved || semantics_dirty {
288 let mut applier = app.composition.applier_mut();
289 match cranpose_ui::update_semantics_tree_from_applier(
290 &mut applier,
291 root,
292 &mut self.semantics_tree,
293 ) {
294 Ok(()) => {
295 #[cfg(debug_assertions)]
296 crate::semantics_check::assert_matches_rebuild(
297 &mut applier,
298 root,
299 self.semantics_tree.as_ref(),
300 );
301 self.semantics_moved = false;
302 app.semantics_snapshot_revision =
303 app.semantics_snapshot_revision.wrapping_add(1);
304 }
305 Err(err) => {
306 log::debug!("failed to build semantics snapshot: {err}");
307 return None;
308 }
309 }
310 }
311 self.semantics_tree.as_ref()
312 }
313}
314
315#[derive(Default)]
316pub(crate) struct TextInputRoutes {
317 active: Option<RootId>,
318 shown: Option<RootId>,
319 handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
320}
321
322impl TextInputRoutes {
323 pub(crate) fn active(&self) -> RootId {
324 self.active.unwrap_or(RootId::Primary)
325 }
326
327 pub(crate) fn set_active(&mut self, root: RootId) {
328 self.active = Some(root);
329 }
330
331 pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
332 self.remove(root);
333 self.handlers.push((root, handler));
334 }
335
336 pub(crate) fn remove(&mut self, root: RootId) {
337 self.handlers.retain(|(id, _)| *id != root);
338 if self.shown == Some(root) {
339 self.shown = None;
340 }
341 }
342
343 fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
344 self.handlers
345 .iter()
346 .find(|(id, _)| *id == root)
347 .map(|(_, handler)| Rc::clone(handler))
348 }
349
350 fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
351 let active = self.active();
352 let handler = self.handler(active);
353 if handler.is_some() {
354 self.shown = Some(active);
355 }
356 handler
357 }
358
359 fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
360 let target = self.shown.take().unwrap_or_else(|| self.active());
361 self.handler(target)
362 }
363}
364
365pub(crate) struct TextInputRouter {
366 pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
367}
368
369impl PlatformTextInputHandler for TextInputRouter {
370 fn show_keyboard(&self) {
371 let handler = self.routes.borrow_mut().take_show_target();
372 if let Some(handler) = handler {
373 handler.show_keyboard();
374 }
375 }
376
377 fn hide_keyboard(&self) {
378 let handler = self.routes.borrow_mut().take_hide_target();
379 if let Some(handler) = handler {
380 handler.hide_keyboard();
381 }
382 }
383}
384
385pub(crate) fn partition_nodes_by_surface<R: Renderer>(
386 app: &mut ShellApp,
387 surfaces: &[RootSurface<R>],
388 nodes: Vec<NodeId>,
389) -> Vec<Vec<NodeId>> {
390 let mut buckets: Vec<Vec<NodeId>> = surfaces.iter().map(|_| Vec::new()).collect();
391 let Some(primary_root) = app.composition.root() else {
392 return buckets;
393 };
394 let mut applier = app.composition.applier_mut();
395 let attached: Vec<NodeId> = applier
396 .scene_nodes_attached_to(nodes, primary_root)
397 .into_iter()
398 .flatten()
399 .collect();
400 let owners = cranpose_ui::nearest_window_roots(&mut applier, &attached);
401 for (node, owner) in attached.into_iter().zip(owners) {
402 if let Some(index) = surfaces
403 .iter()
404 .position(|surface| surface.owns_nodes_under(owner))
405 {
406 buckets[index].push(node);
407 }
408 }
409 buckets
410}
411
412pub struct SurfaceMut<'a, R: Renderer> {
419 pub(crate) shell: &'a mut AppShell<R>,
420 pub(crate) index: usize,
421}
422
423impl<'a, R> SurfaceMut<'a, R>
424where
425 R: Renderer,
426 R::Error: Debug,
427{
428 pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
429 Self { shell, index }
430 }
431
432 pub fn shell(&mut self) -> &mut AppShell<R> {
435 self.shell
436 }
437
438 pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
439 &mut self.shell.app
440 }
441
442 pub(crate) fn shell_app_ref(&self) -> &ShellApp {
443 &self.shell.app
444 }
445
446 pub(crate) fn surface(&self) -> &RootSurface<R> {
447 &self.shell.surfaces[self.index]
448 }
449
450 pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
451 &mut self.shell.surfaces[self.index]
452 }
453
454 pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
455 let shell = &mut *self.shell;
456 (&mut shell.app, &mut shell.surfaces[self.index])
457 }
458
459 pub fn id(&self) -> RootId {
461 self.surface().id
462 }
463
464 pub fn root(&self) -> Option<NodeId> {
466 let surface = self.surface();
467 surface.root_node(self.shell_app_ref())
468 }
469
470 pub fn renderer(&mut self) -> &mut R {
472 &mut self.surface_mut().renderer
473 }
474
475 pub fn scene(&self) -> &R::Scene {
477 self.surface().renderer.scene()
478 }
479
480 pub fn set_viewport(&mut self, width: f32, height: f32) {
488 self.surface_mut().viewport = (width, height);
489 match self.id() {
490 RootId::Primary => self.shell_app().request_forced_layout_pass(),
491 RootId::Window(_) => {
492 if let Some(root) = self.root() {
493 let app_context = Rc::clone(&self.shell_app_ref().app_context);
494 app_context.enter(|| cranpose_ui::schedule_measure_repass(root));
495 }
496 self.shell_app().request_layout_pass();
497 }
498 }
499 self.surface_mut().scene_dirty = true;
500 self.mark_dirty();
501 }
502
503 pub fn set_screen_origin(&mut self, origin: Option<Point>) {
509 self.surface_mut().screen_origin = origin;
510 }
511
512 pub fn screen_origin(&self) -> Option<Point> {
515 self.surface().screen_origin
516 }
517
518 pub fn viewport_size(&self) -> (f32, f32) {
520 self.surface().viewport
521 }
522
523 pub fn set_buffer_size(&mut self, width: u32, height: u32) {
525 self.surface_mut().buffer_size = (width, height);
526 }
527
528 pub fn buffer_size(&self) -> (u32, u32) {
530 self.surface().buffer_size
531 }
532
533 pub fn mark_dirty(&mut self) {
535 self.surface_mut().is_dirty = true;
536 }
537
538 pub fn needs_redraw(&self) -> bool {
542 let app_context = Rc::clone(&self.shell_app_ref().app_context);
543 app_context.enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
544 }
545
546 pub fn has_active_pointer_gesture(&self) -> bool {
549 self.surface().has_active_pointer_gesture()
550 }
551
552 pub fn last_update_result(&self) -> FrameUpdateResult {
555 self.surface().last_update
556 }
557
558 pub fn frame_owed(&self) -> bool {
563 self.surface().frame_owed
564 }
565
566 pub fn take_frame_owed(&mut self) -> bool {
568 std::mem::take(&mut self.surface_mut().frame_owed)
569 }
570
571 fn compute_frame_schedule(&self) -> FrameSchedule {
572 self.surface()
573 .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
574 }
575
576 pub fn frame_schedule(&self) -> FrameSchedule {
579 let schedule = self.compute_frame_schedule();
580 self.surface().frame_scheduler.record(schedule);
581 schedule
582 }
583
584 pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
586 where
587 D: PlatformFrameDriver + ?Sized,
588 {
589 let schedule = self.compute_frame_schedule();
590 self.surface().frame_scheduler.schedule(schedule, driver);
591 schedule
592 }
593
594 pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
596 self.surface().frame_scheduler.snapshot()
597 }
598
599 pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
602 self.surface_mut().frame_rate_preference = preference;
603 }
604
605 pub fn frame_rate_preference(&self) -> FrameRatePreference {
607 self.surface().frame_rate_preference
608 }
609
610 pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
613 self.surface().dev_overlay_control_center(mode)
614 }
615
616 pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
617 if !self.shell_app_ref().dev_options.frame_pacing_controls {
618 return false;
619 }
620 let Some(mode) = self
621 .surface()
622 .dev_overlay_controls
623 .iter()
624 .find(|control| control.bounds.contains(x, y))
625 .map(|control| control.mode)
626 else {
627 return false;
628 };
629 self.shell().set_frame_pacing_mode(mode);
630 true
631 }
632
633 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
635 let (app, surface) = self.parts();
636 let app_context = Rc::clone(&app.app_context);
637 app_context.enter(|| block(surface.layout_tree_in_context(app)))
638 }
639
640 pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
643 let (app, surface) = self.parts();
644 let app_context = Rc::clone(&app.app_context);
645 app_context.enter(|| block(surface.semantics_tree_in_context(app)))
646 }
647
648 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
651 self.surface().pointer_icon.take_change()
652 }
653
654 pub fn refresh_pointer_icon(&self) {
657 self.surface().pointer_icon.refresh();
658 }
659
660 pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
665 let id = self.id();
666 let app = self.shell_app();
667 app.text_input_routes.borrow_mut().set_handler(id, handler);
668 app.install_text_input_router();
669 }
670
671 pub fn activate(&mut self) {
674 let id = self.id();
675 self.shell_app()
676 .text_input_routes
677 .borrow_mut()
678 .set_active(id);
679 }
680}