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