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::new(),
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 semantics_tree_in_context(
247 &mut self,
248 app: &mut ShellApp,
249 ) -> Option<&SemanticsTree> {
250 if !app.semantics_enabled && !self.inspector.state.open {
251 return None;
252 }
253 self.semantics_tree_for_input(app)
254 }
255
256 pub(crate) fn semantics_tree_for_input(
257 &mut self,
258 app: &mut ShellApp,
259 ) -> Option<&SemanticsTree> {
260 let root = self.root_node(app)?;
261 let semantics_dirty = {
262 let mut applier = app.composition.applier_mut();
263 cranpose_ui::tree_needs_semantics(&mut *applier, root).unwrap_or_else(|err| {
264 log::debug!("failed to check semantics dirty status for root #{root}: {err}");
265 true
266 })
267 };
268 if self.semantics_tree.is_none() || semantics_dirty {
269 let mut applier = app.composition.applier_mut();
270 match cranpose_ui::build_semantics_tree_from_applier(&mut applier, root) {
271 Ok(semantics_tree) => {
272 self.semantics_tree = semantics_tree;
273 app.semantics_snapshot_revision =
274 app.semantics_snapshot_revision.wrapping_add(1);
275 }
276 Err(err) => {
277 log::debug!("failed to build semantics snapshot: {err}");
278 return None;
279 }
280 }
281 }
282 self.semantics_tree.as_ref()
283 }
284}
285
286#[derive(Default)]
287pub(crate) struct TextInputRoutes {
288 active: Option<RootId>,
289 shown: Option<RootId>,
290 handlers: Vec<(RootId, Rc<dyn PlatformTextInputHandler>)>,
291}
292
293impl TextInputRoutes {
294 pub(crate) fn active(&self) -> RootId {
295 self.active.unwrap_or(RootId::Primary)
296 }
297
298 pub(crate) fn set_active(&mut self, root: RootId) {
299 self.active = Some(root);
300 }
301
302 pub(crate) fn set_handler(&mut self, root: RootId, handler: Rc<dyn PlatformTextInputHandler>) {
303 self.remove(root);
304 self.handlers.push((root, handler));
305 }
306
307 pub(crate) fn remove(&mut self, root: RootId) {
308 self.handlers.retain(|(id, _)| *id != root);
309 if self.shown == Some(root) {
310 self.shown = None;
311 }
312 }
313
314 fn handler(&self, root: RootId) -> Option<Rc<dyn PlatformTextInputHandler>> {
315 self.handlers
316 .iter()
317 .find(|(id, _)| *id == root)
318 .map(|(_, handler)| Rc::clone(handler))
319 }
320
321 fn take_show_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
322 let active = self.active();
323 let handler = self.handler(active);
324 if handler.is_some() {
325 self.shown = Some(active);
326 }
327 handler
328 }
329
330 fn take_hide_target(&mut self) -> Option<Rc<dyn PlatformTextInputHandler>> {
331 let target = self.shown.take().unwrap_or_else(|| self.active());
332 self.handler(target)
333 }
334}
335
336pub(crate) struct TextInputRouter {
337 pub(crate) routes: Rc<RefCell<TextInputRoutes>>,
338}
339
340impl PlatformTextInputHandler for TextInputRouter {
341 fn show_keyboard(&self) {
342 let handler = self.routes.borrow_mut().take_show_target();
343 if let Some(handler) = handler {
344 handler.show_keyboard();
345 }
346 }
347
348 fn hide_keyboard(&self) {
349 let handler = self.routes.borrow_mut().take_hide_target();
350 if let Some(handler) = handler {
351 handler.hide_keyboard();
352 }
353 }
354}
355
356pub(crate) fn partition_nodes_by_surface<R: Renderer>(
357 app: &mut ShellApp,
358 surfaces: &[RootSurface<R>],
359 nodes: Vec<NodeId>,
360) -> Vec<Vec<NodeId>> {
361 let mut buckets: Vec<Vec<NodeId>> = surfaces.iter().map(|_| Vec::new()).collect();
362 let Some(primary_root) = app.composition.root() else {
363 return buckets;
364 };
365 let mut applier = app.composition.applier_mut();
366 for node in nodes {
367 let Some(node) = applier.scene_node_attached_to(node, primary_root) else {
368 continue;
369 };
370 let owner = cranpose_ui::nearest_window_root(&mut applier, node);
371 if let Some(index) = surfaces
372 .iter()
373 .position(|surface| surface.owns_nodes_under(owner))
374 {
375 buckets[index].push(node);
376 }
377 }
378 buckets
379}
380
381pub struct SurfaceMut<'a, R: Renderer> {
388 pub(crate) shell: &'a mut AppShell<R>,
389 pub(crate) index: usize,
390}
391
392impl<'a, R> SurfaceMut<'a, R>
393where
394 R: Renderer,
395 R::Error: Debug,
396{
397 pub(crate) fn new(shell: &'a mut AppShell<R>, index: usize) -> Self {
398 Self { shell, index }
399 }
400
401 pub fn shell(&mut self) -> &mut AppShell<R> {
404 self.shell
405 }
406
407 pub(crate) fn shell_app(&mut self) -> &mut ShellApp {
408 &mut self.shell.app
409 }
410
411 pub(crate) fn shell_app_ref(&self) -> &ShellApp {
412 &self.shell.app
413 }
414
415 pub(crate) fn surface(&self) -> &RootSurface<R> {
416 &self.shell.surfaces[self.index]
417 }
418
419 pub(crate) fn surface_mut(&mut self) -> &mut RootSurface<R> {
420 &mut self.shell.surfaces[self.index]
421 }
422
423 pub(crate) fn parts(&mut self) -> (&mut ShellApp, &mut RootSurface<R>) {
424 let shell = &mut *self.shell;
425 (&mut shell.app, &mut shell.surfaces[self.index])
426 }
427
428 pub fn id(&self) -> RootId {
430 self.surface().id
431 }
432
433 pub fn root(&self) -> Option<NodeId> {
435 let surface = self.surface();
436 surface.root_node(self.shell_app_ref())
437 }
438
439 pub fn renderer(&mut self) -> &mut R {
441 &mut self.surface_mut().renderer
442 }
443
444 pub fn scene(&self) -> &R::Scene {
446 self.surface().renderer.scene()
447 }
448
449 pub fn set_viewport(&mut self, width: f32, height: f32) {
457 self.surface_mut().viewport = (width, height);
458 match self.id() {
459 RootId::Primary => self.shell_app().request_forced_layout_pass(),
460 RootId::Window(_) => {
461 if let Some(root) = self.root() {
462 let app_context = Rc::clone(&self.shell_app_ref().app_context);
463 app_context.enter(|| cranpose_ui::schedule_measure_repass(root));
464 }
465 self.shell_app().request_layout_pass();
466 }
467 }
468 self.surface_mut().scene_dirty = true;
469 self.mark_dirty();
470 }
471
472 pub fn set_screen_origin(&mut self, origin: Option<Point>) {
478 self.surface_mut().screen_origin = origin;
479 }
480
481 pub fn screen_origin(&self) -> Option<Point> {
484 self.surface().screen_origin
485 }
486
487 pub fn viewport_size(&self) -> (f32, f32) {
489 self.surface().viewport
490 }
491
492 pub fn set_buffer_size(&mut self, width: u32, height: u32) {
494 self.surface_mut().buffer_size = (width, height);
495 }
496
497 pub fn buffer_size(&self) -> (u32, u32) {
499 self.surface().buffer_size
500 }
501
502 pub fn mark_dirty(&mut self) {
504 self.surface_mut().is_dirty = true;
505 }
506
507 pub fn needs_redraw(&self) -> bool {
511 let app_context = Rc::clone(&self.shell_app_ref().app_context);
512 app_context.enter(|| self.surface().needs_redraw_in_context(self.shell_app_ref()))
513 }
514
515 pub fn has_active_pointer_gesture(&self) -> bool {
518 self.surface().has_active_pointer_gesture()
519 }
520
521 pub fn last_update_result(&self) -> FrameUpdateResult {
524 self.surface().last_update
525 }
526
527 pub fn frame_owed(&self) -> bool {
532 self.surface().frame_owed
533 }
534
535 pub fn take_frame_owed(&mut self) -> bool {
537 std::mem::take(&mut self.surface_mut().frame_owed)
538 }
539
540 fn compute_frame_schedule(&self) -> FrameSchedule {
541 self.surface()
542 .compute_frame_schedule(self.shell_app_ref(), self.shell.any_surface_dirty())
543 }
544
545 pub fn frame_schedule(&self) -> FrameSchedule {
548 let schedule = self.compute_frame_schedule();
549 self.surface().frame_scheduler.record(schedule);
550 schedule
551 }
552
553 pub fn schedule_platform_frame<D>(&self, driver: &D) -> FrameSchedule
555 where
556 D: PlatformFrameDriver + ?Sized,
557 {
558 let schedule = self.compute_frame_schedule();
559 self.surface().frame_scheduler.schedule(schedule, driver);
560 schedule
561 }
562
563 pub fn frame_scheduler_snapshot(&self) -> FrameSchedule {
565 self.surface().frame_scheduler.snapshot()
566 }
567
568 pub fn set_frame_rate_preference(&mut self, preference: FrameRatePreference) {
571 self.surface_mut().frame_rate_preference = preference;
572 }
573
574 pub fn frame_rate_preference(&self) -> FrameRatePreference {
576 self.surface().frame_rate_preference
577 }
578
579 pub fn dev_overlay_control_center(&self, mode: FramePacingMode) -> Option<(f32, f32)> {
582 self.surface().dev_overlay_control_center(mode)
583 }
584
585 pub(crate) fn dev_overlay_press(&mut self, x: f32, y: f32) -> bool {
586 if !self.shell_app_ref().dev_options.frame_pacing_controls {
587 return false;
588 }
589 let Some(mode) = self
590 .surface()
591 .dev_overlay_controls
592 .iter()
593 .find(|control| control.bounds.contains(x, y))
594 .map(|control| control.mode)
595 else {
596 return false;
597 };
598 self.shell().set_frame_pacing_mode(mode);
599 true
600 }
601
602 pub fn with_layout_tree<T>(&mut self, block: impl FnOnce(Option<&LayoutTree>) -> T) -> T {
604 let (app, surface) = self.parts();
605 let app_context = Rc::clone(&app.app_context);
606 app_context.enter(|| block(surface.layout_tree_in_context(app)))
607 }
608
609 pub fn with_semantics_tree<T>(&mut self, block: impl FnOnce(Option<&SemanticsTree>) -> T) -> T {
612 let (app, surface) = self.parts();
613 let app_context = Rc::clone(&app.app_context);
614 app_context.enter(|| block(surface.semantics_tree_in_context(app)))
615 }
616
617 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
620 self.surface().pointer_icon.take_change()
621 }
622
623 pub fn refresh_pointer_icon(&self) {
626 self.surface().pointer_icon.refresh()
627 }
628
629 pub fn set_platform_text_input(&mut self, handler: Rc<dyn PlatformTextInputHandler>) {
634 let id = self.id();
635 let app = self.shell_app();
636 app.text_input_routes.borrow_mut().set_handler(id, handler);
637 app.install_text_input_router();
638 }
639
640 pub fn activate(&mut self) {
643 let id = self.id();
644 self.shell_app()
645 .text_input_routes
646 .borrow_mut()
647 .set_active(id);
648 }
649}