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.invalidate_scene_root(root);
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.is_dirty = true;
170 }
171
172 pub(crate) fn invalidate_scene_root(&mut self, root: Option<NodeId>) {
173 self.scoped_layout_scene_nodes.clear();
174 self.scoped_layout_scene_nodes.extend(root);
175 self.scene_dirty = true;
176 }
177
178 pub(crate) fn forget_snapshots(&mut self) {
181 self.layout_tree = None;
182 self.semantics_moved = true;
183 }
184
185 pub(crate) fn has_active_pointer_gesture(&self) -> bool {
186 self.buttons_pressed != PointerButtons::NONE
187 && self.hit_path_tracker.has_path(PointerId::PRIMARY)
188 }
189
190 pub(crate) fn renderer_warmup_due(&self, app: &ShellApp) -> bool {
191 self.renderer.needs_frame_warmup() && !app.runtime.runtime_handle().has_frame_callbacks()
192 }
193
194 pub(crate) fn needs_redraw_in_context(&self, app: &ShellApp) -> bool {
195 app.has_stale_work_in_context()
196 || app.composition.should_recompose()
197 || self.is_dirty
198 || self.scene_dirty
199 || self.renderer_warmup_due(app)
200 }
201
202 pub(crate) fn compute_frame_schedule(
203 &self,
204 app: &ShellApp,
205 surfaces_dirty: bool,
206 ) -> FrameSchedule {
207 let app_context = Rc::clone(&app.app_context);
208 let (needs_update, needs_frame) = app_context.enter(|| {
209 let needs_frame = self.is_dirty
210 || self.scene_dirty
211 || app.wants_frame_in_context()
212 || self.has_active_pointer_gesture()
213 || self.renderer_warmup_due(app);
214 (app.needs_ui_update_in_context(surfaces_dirty), needs_frame)
215 });
216 FrameSchedule {
217 needs_update,
218 needs_frame,
219 next_deadline: app.next_event_time(),
220 }
221 }
222
223 pub(crate) fn invalidate_dev_overlay_text(&mut self) {
224 self.dev_overlay_text.clear();
225 self.dev_overlay_last_refresh = None;
226 self.dev_overlay_viewport = None;
227 }
228
229 pub(crate) fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
230 self.dev_overlay_controls
231 .iter()
232 .find(|control| control.mode == mode)
233 .map(|control| {
234 (
235 control.bounds.x + control.bounds.width * 0.5,
236 control.bounds.y + control.bounds.height * 0.5,
237 )
238 })
239 }
240
241 pub(crate) fn layout_tree_in_context(&mut self, app: &mut ShellApp) -> Option<&LayoutTree> {
242 if self.layout_tree.is_none() {
243 let root = self.root_node(app)?;
244 let mut applier = app.composition.applier_mut();
245 match cranpose_ui::build_layout_tree_from_applier(&mut applier, root) {
246 Ok(layout_tree) => {
247 self.layout_tree = layout_tree;
248 }
249 Err(err) => {
250 log::debug!("failed to build layout snapshot: {err}");
251 return None;
252 }
253 }
254 }
255 self.layout_tree.as_ref()
256 }
257
258 pub(crate) fn top_modal(&self, app: &mut ShellApp) -> Option<NodeId> {
261 let root = self.root_node(app)?;
262 let mut applier = app.composition.applier_mut();
263 cranpose_ui::top_modal_from_applier(&mut applier, root).unwrap_or_else(|err| {
264 log::debug!("failed to find the top modal under root #{root}: {err}");
265 None
266 })
267 }
268
269 pub(crate) fn semantics_tree_in_context(
270 &mut self,
271 app: &mut ShellApp,
272 ) -> Option<&SemanticsTree> {
273 if !app.semantics_enabled && !self.inspector.state.open {
274 return None;
275 }
276 self.semantics_tree_for_input(app)
277 }
278
279 pub(crate) fn semantics_tree_for_input(
280 &mut self,
281 app: &mut ShellApp,
282 ) -> Option<&SemanticsTree> {
283 app.flush_semantics_invalidations();
284 let root = self.root_node(app)?;
285 let semantics_dirty = {
286 let mut applier = app.composition.applier_mut();
287 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
288 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
289 true
290 })
291 };
292 if self.semantics_tree.is_none() || self.semantics_moved || semantics_dirty {
293 let mut applier = app.composition.applier_mut();
294 match cranpose_ui::update_semantics_tree_from_applier(
295 &mut applier,
296 root,
297 &mut self.semantics_tree,
298 ) {
299 Ok(()) => {
300 #[cfg(debug_assertions)]
301 crate::semantics_check::assert_matches_rebuild(
302 &mut applier,
303 root,
304 self.semantics_tree.as_ref(),
305 );
306 self.semantics_moved = false;
307 app.semantics_snapshot_revision =
308 app.semantics_snapshot_revision.wrapping_add(1);
309 }
310 Err(err) => {
311 log::debug!("failed to build semantics snapshot: {err}");
312 return None;
313 }
314 }
315 }
316 self.semantics_tree.as_ref()
317 }
318}
319
320#[derive(Default)]
321pub(crate) struct TextInputRoutes {
322 active: Option<RootId>,
323 shown: Option<RootId>,
324 handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
325}
326
327impl TextInputRoutes {
328 pub(crate) fn active(&self) -> RootId {
329 self.active.unwrap_or(RootId::Primary)
330 }
331
332 pub(crate) fn set_active(&mut self, root: RootId) {
333 self.active = Some(root);
334 }
335
336 pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
337 self.remove(root);
338 self.handlers.push((root, handler));
339 }
340
341 pub(crate) fn remove(&mut self, root: RootId) {
342 self.handlers.retain(|(id, _)| *id != root);
343 if self.shown == Some(root) {
344 self.shown = None;
345 }
346 }
347
348 fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
349 self.handlers
350 .iter()
351 .find(|(id, _)| *id == root)
352 .map(|(_, handler)| Rc::clone(handler))
353 }
354
355 fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
356 let active = self.active();
357 let handler = self.handler(active);
358 if handler.is_some() {
359 self.shown = Some(active);
360 }
361 handler
362 }
363
364 fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
365 let target = self.shown.take().unwrap_or_else(|| self.active());
366 self.handler(target)
367 }
368}
369
370pub(crate) struct TextInputRouter {
371 pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
372}
373
374impl PlatformTextInputHandler for TextInputRouter {
375 fn show_keyboard(&self) {
376 let handler = self.routes.borrow_mut().take_show_target();
377 if let Some(handler) = handler {
378 handler.show_keyboard();
379 }
380 }
381
382 fn hide_keyboard(&self) {
383 let handler = self.routes.borrow_mut().take_hide_target();
384 if let Some(handler) = handler {
385 handler.hide_keyboard();
386 }
387 }
388}
389
390pub(crate) fn partition_nodes_by_surface<R: Renderer>(
391 app: &mut ShellApp,
392 surfaces: &[RootSurface<R>],
393 nodes: Vec<NodeId>,
394) -> Vec<Vec<NodeId>> {
395 let mut buckets: Vec<Vec<NodeId>> = surfaces.iter().map(|_| Vec::new()).collect();
396 let Some(primary_root) = app.composition.root() else {
397 return buckets;
398 };
399 let mut applier = app.composition.applier_mut();
400 let attached: Vec<NodeId> = applier
401 .scene_nodes_attached_to(nodes, primary_root)
402 .into_iter()
403 .flatten()
404 .collect();
405 let owners = cranpose_ui::nearest_window_roots(&mut applier, &attached);
406 for (node, owner) in attached.into_iter().zip(owners) {
407 if let Some(index) = surfaces
408 .iter()
409 .position(|surface| surface.owns_nodes_under(owner))
410 {
411 buckets[index].push(node);
412 }
413 }
414 buckets
415}
416
417pub struct SurfaceMut<'a, R: Renderer> {
424 pub(crate) shell: &'a mut AppShell<R>,
425 pub(crate) index: usize,
426}
427
428impl<'a, R> SurfaceMut<'a, R>
429where
430 R: Renderer,
431 R::Error: Debug,
432{
433 pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
434 Self { shell, index }
435 }
436
437 pub fn shell(&mut self) -> &mut AppShell<R> {
440 self.shell
441 }
442
443 pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
444 &mut self.shell.app
445 }
446
447 pub(crate) fn shell_app_ref(&self) -> &ShellApp {
448 &self.shell.app
449 }
450
451 pub(crate) fn surface(&self) -> &RootSurface<R> {
452 &self.shell.surfaces[self.index]
453 }
454
455 pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
456 &mut self.shell.surfaces[self.index]
457 }
458
459 pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
460 let shell = &mut *self.shell;
461 (&mut shell.app, &mut shell.surfaces[self.index])
462 }
463
464 pub fn id(&self) -> RootId {
466 self.surface().id
467 }
468
469 pub fn root(&self) -> Option<NodeId> {
471 let surface = self.surface();
472 surface.root_node(self.shell_app_ref())
473 }
474
475 pub fn renderer(&mut self) -> &mut R {
477 &mut self.surface_mut().renderer
478 }
479
480 pub fn with_renderer<T>(&mut self, block: impl FnOnce(&mut R) -> T) -> T {
483 let (app, surface) = self.parts();
484 app.app_context.enter(|| block(&mut surface.renderer))
485 }
486
487 pub fn scene(&self) -> &R::Scene {
489 self.surface().renderer.scene()
490 }
491
492 pub fn set_viewport(&mut self, width: f32, height: f32) {
500 self.surface_mut().viewport = (width, height);
501 match self.id() {
502 RootId::Primary => self.shell_app().request_forced_layout_pass(),
503 RootId::Window(_) => {
504 if let Some(root) = self.root() {
505 let app_context = Rc::clone(&self.shell_app_ref().app_context);
506 app_context.enter(|| cranpose_ui::schedule_measure_repass(root));
507 }
508 self.shell_app().request_layout_pass();
509 }
510 }
511 let root = self.root();
512 self.surface_mut().invalidate_scene_root(root);
513 self.mark_dirty();
514 }
515
516 pub fn set_screen_origin(&mut self, origin: Option<Point>) {
522 self.surface_mut().screen_origin = origin;
523 }
524
525 pub fn screen_origin(&self) -> Option<Point> {
528 self.surface().screen_origin
529 }
530
531 pub fn viewport_size(&self) -> (f32, f32) {
533 self.surface().viewport
534 }
535
536 pub fn set_buffer_size(&mut self, width: u32, height: u32) {
538 self.surface_mut().buffer_size = (width, height);
539 }
540
541 pub fn buffer_size(&self) -> (u32, u32) {
543 self.surface().buffer_size
544 }
545
546 pub fn mark_dirty(&mut self) {
548 self.surface_mut().is_dirty = true;
549 }
550
551 pub fn needs_redraw(&self) -> bool {
555 let app_context = Rc::clone(&self.shell_app_ref().app_context);
556 app_context.enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
557 }
558
559 pub fn has_active_pointer_gesture(&self) -> bool {
562 self.surface().has_active_pointer_gesture()
563 }
564
565 pub fn last_update_result(&self) -> FrameUpdateResult {
568 self.surface().last_update
569 }
570
571 pub fn frame_owed(&self) -> bool {
576 self.surface().frame_owed
577 }
578
579 pub fn take_frame_owed(&mut self) -> bool {
581 std::mem::take(&mut self.surface_mut().frame_owed)
582 }
583
584 fn compute_frame_schedule(&self) -> FrameSchedule {
585 self.surface()
586 .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
587 }
588
589 pub fn frame_schedule(&self) -> FrameSchedule {
592 let schedule = self.compute_frame_schedule();
593 self.surface().frame_scheduler.record(schedule);
594 schedule
595 }
596
597 pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
599 where
600 D: PlatformFrameDriver + ?Sized,
601 {
602 let schedule = self.compute_frame_schedule();
603 self.surface().frame_scheduler.schedule(schedule, driver);
604 schedule
605 }
606
607 pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
609 self.surface().frame_scheduler.snapshot()
610 }
611
612 pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
615 self.surface_mut().frame_rate_preference = preference;
616 }
617
618 pub fn frame_rate_preference(&self) -> FrameRatePreference {
620 self.surface().frame_rate_preference
621 }
622
623 pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
626 self.surface().dev_overlay_control_center(mode)
627 }
628
629 pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
630 if !self.shell_app_ref().dev_options.frame_pacing_controls {
631 return false;
632 }
633 let Some(mode) = self
634 .surface()
635 .dev_overlay_controls
636 .iter()
637 .find(|control| control.bounds.contains(x, y))
638 .map(|control| control.mode)
639 else {
640 return false;
641 };
642 self.shell().set_frame_pacing_mode(mode);
643 true
644 }
645
646 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
648 let (app, surface) = self.parts();
649 let app_context = Rc::clone(&app.app_context);
650 app_context.enter(|| block(surface.layout_tree_in_context(app)))
651 }
652
653 pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
656 let (app, surface) = self.parts();
657 let app_context = Rc::clone(&app.app_context);
658 app_context.enter(|| block(surface.semantics_tree_in_context(app)))
659 }
660
661 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
664 self.surface().pointer_icon.take_change()
665 }
666
667 pub fn refresh_pointer_icon(&self) {
670 self.surface().pointer_icon.refresh();
671 }
672
673 pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
678 let id = self.id();
679 let app = self.shell_app();
680 app.text_input_routes.borrow_mut().set_handler(id, handler);
681 app.install_text_input_router();
682 }
683
684 pub fn activate(&mut self) {
687 let id = self.id();
688 self.shell_app()
689 .text_input_routes
690 .borrow_mut()
691 .set_active(id);
692 }
693}