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