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