Skip to main content

cranpose_app_shell/
shell_input.rs

1use cranpose_ui::FocusDirection;
2
3use super::*;
4
5impl<R> SurfaceMut<'_, R>
6where
7    R: Renderer,
8    R::Error: Debug,
9{
10    fn pointer_event(
11        &self,
12        kind: PointerEventKind,
13        position: Point,
14        global_position: Point,
15        event_time: PointerEventTime,
16    ) -> PointerEvent {
17        let screen_position = self.surface().screen_origin.map(|origin| Point {
18            x: origin.x + global_position.x,
19            y: origin.y + global_position.y,
20        });
21        let mut event = PointerEvent::new(kind, position, global_position)
22            .with_time_ms(event_time.platform_time_ms)
23            .with_animation_time_nanos(event_time.animation_time_nanos)
24            .with_screen_position(screen_position);
25        event.modifiers = self.shell.app.modifiers;
26        event
27    }
28
29    fn resolve_gesture_targets(
30        &self,
31        pointer: PointerId,
32    ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
33        self.resolve_hit_path(pointer)
34    }
35
36    fn resolve_hit_path(
37        &self,
38        pointer: PointerId,
39    ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
40        let Some(node_ids) = self.surface().hit_path_tracker.dispatch_order(pointer) else {
41            return Vec::new();
42        };
43
44        let scene = self.surface().renderer.scene();
45        let targets: Vec<_> = node_ids
46            .iter()
47            .filter_map(|&id| scene.find_target(id))
48            .collect();
49        log::trace!(
50            target: "cranpose::input",
51            "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
52            targets.len()
53        );
54        targets
55    }
56
57    fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
58    where
59        I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
60    {
61        let mut applier = self.shell.app.composition.applier_mut();
62        for target in targets {
63            let node_id = target.node_id();
64            target.dispatch_with_applier(&mut applier, event.clone());
65            log::trace!(
66                target: "cranpose::input",
67                "dispatch {:?} node={} consumed={} stop_on_consume={}",
68                event.kind,
69                node_id,
70                event.is_consumed(),
71                stop_on_consume,
72            );
73            if stop_on_consume && event.is_consumed() {
74                break;
75            }
76        }
77        event.finish_post_dispatch();
78    }
79
80    fn dispatch_to_hits(
81        &mut self,
82        hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
83        event: PointerEvent,
84    ) -> bool {
85        let capture_paths = hits
86            .iter()
87            .map(cranpose_render_common::HitTestTarget::capture_path)
88            .collect::<Vec<_>>();
89        let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
90            .into_iter()
91            .filter_map(|node_id| self.surface().renderer.scene().find_target(node_id))
92            .collect::<Vec<_>>();
93
94        self.dispatch_targets(targets, event.clone(), true);
95
96        event.is_consumed()
97    }
98
99    /// Sets the device source (touch/mouse/stylus) of the pointer sample that
100    /// the platform is about to dispatch. Call this before `set_cursor` /
101    /// `pointer_pressed` / `pointer_released` so the resulting `PointerEvent`s
102    /// carry the source so consumers can preserve device-specific gesture
103    /// details without changing shared pointer UI.
104    pub fn set_pointer_source(&mut self, source: PointerSource) {
105        self.surface_mut().pointer_source = source;
106    }
107
108    /// The device source of the most recent pointer sample.
109    pub fn pointer_source(&self) -> PointerSource {
110        self.surface().pointer_source
111    }
112
113    /// Moves the pointer to `(x, y)` in logical pixels and sends the enter, exit, move or drag
114    /// events that follow. Returns `true` when a control or overlay handled the move.
115    pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
116        self.set_cursor_at_time(x, y, None)
117    }
118
119    /// Like [`set_cursor`](Self::set_cursor), but carries the platform input
120    /// timestamp (milliseconds, platform-specific time base) of the sample.
121    ///
122    /// Platforms that deliver input batched/frame-aligned (Android) must use
123    /// this so gesture velocity is computed from real event times instead of
124    /// delivery times.
125    pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
126        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
127        self.set_cursor_at_event_time(x, y, event_time)
128    }
129
130    /// Set the cursor using a timestamp already resolved into both clock domains.
131    pub fn set_cursor_at_event_time(
132        &mut self,
133        x: f32,
134        y: f32,
135        event_time: PointerEventTime,
136    ) -> bool {
137        let _event_handler = enter_event_handler_scope();
138        let result = {
139            let _app_context = self.shell.app.app_context.enter_scope();
140            run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
141        };
142        self.route_drag_and_drop();
143        if result {
144            self.mark_dirty();
145        }
146        log::trace!(
147            target: "cranpose::input",
148            "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
149            event_time.platform_time_ms,
150            event_time.animation_time_nanos,
151        );
152        result
153    }
154
155    fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
156        self.surface_mut().cursor = (x, y);
157        if self.inspector_move(x, y) {
158            return true;
159        }
160
161        if self.surface().buttons_pressed != PointerButtons::NONE {
162            if self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
163                let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
164                if !targets.is_empty() {
165                    let event = self
166                        .pointer_event(
167                            PointerEventKind::Move,
168                            Point { x, y },
169                            Point { x, y },
170                            event_time,
171                        )
172                        .with_buttons(self.surface().buttons_pressed)
173                        .with_source(self.surface().pointer_source);
174                    self.dispatch_targets(targets, event, false);
175                    return true;
176                }
177
178                return false;
179            }
180
181            return false;
182        }
183
184        let hits = if self.inspector_blocks_pointer(x, y) {
185            Vec::new()
186        } else {
187            self.surface().renderer.scene().hit_test(x, y)
188        };
189        let new_ids: Vec<NodeId> = hits
190            .iter()
191            .map(cranpose_render_common::HitTestTarget::node_id)
192            .collect();
193
194        let pos = Point { x, y };
195        let previously_hovered = self.surface().hovered_nodes.clone();
196        for old_id in previously_hovered {
197            if !new_ids.contains(&old_id)
198                && let Some(target) = self.surface().renderer.scene().find_target(old_id)
199            {
200                let exit_event = self
201                    .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
202                    .with_buttons(self.surface().buttons_pressed)
203                    .with_source(self.surface().pointer_source);
204                self.dispatch_targets(std::iter::once(target), exit_event, false);
205            }
206        }
207
208        for hit in &hits {
209            if !self.surface().hovered_nodes.contains(&hit.node_id()) {
210                let enter_event = self
211                    .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
212                    .with_buttons(self.surface().buttons_pressed)
213                    .with_source(self.surface().pointer_source);
214                self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
215            }
216        }
217
218        self.surface_mut().hovered_nodes = new_ids;
219        self.apply_hovered_pointer_icon(&hits);
220
221        if !hits.is_empty() {
222            let event = self
223                .pointer_event(PointerEventKind::Move, pos, pos, event_time)
224                .with_buttons(self.surface().buttons_pressed)
225                .with_source(self.surface().pointer_source);
226            self.dispatch_targets(hits, event, true);
227            true
228        } else {
229            false
230        }
231    }
232
233    /// Activates the identified control using its current semantics and geometry.
234    ///
235    /// Hidden, disabled, removed and modal-background controls reject activation.
236    /// `canvas_key` identifies a drawn child of `node_id`. The action bypasses
237    /// overlapping controls and developer overlays without changing pointer capture.
238    pub fn accessibility_activate(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> bool {
239        let event_time = self.shell.app.realtime_pointer_event_time(None);
240        let _event_handler = enter_event_handler_scope();
241        let result = {
242            let _app_context = self.shell.app.app_context.enter_scope();
243            run_in_mutable_snapshot(|| self.activate_node(node_id, canvas_key, event_time))
244                .unwrap_or(false)
245        };
246        if result {
247            self.mark_dirty();
248        }
249        result
250    }
251
252    fn activate_node(
253        &mut self,
254        node_id: NodeId,
255        canvas_key: Option<u64>,
256        event_time: PointerEventTime,
257    ) -> bool {
258        let target = {
259            let (app, surface) = self.parts();
260            surface
261                .semantics_tree_for_input(app)
262                .and_then(|tree| activation_target(tree.root(), node_id, canvas_key))
263        };
264        let (target_id, canvas_bounds) = match target {
265            Some(ActivationTarget::Direct(action)) => {
266                action.invoke();
267                return true;
268            }
269            Some(ActivationTarget::Edit(node_id)) => {
270                self.activate();
271                return cranpose_ui::request_focus_from_platform(node_id);
272            }
273            Some(ActivationTarget::Pointer(node_id, bounds)) => (node_id, bounds),
274            None => return false,
275        };
276        let position =
277            self.with_layout_tree(|tree| activation_input(tree?.root(), target_id, canvas_bounds));
278        let Some((position, local, handlers)) = position else {
279            return false;
280        };
281        for kind in [PointerEventKind::Down, PointerEventKind::Up] {
282            let event = self.pointer_event(kind, local, position, event_time);
283            handlers.dispatch_pointer_event(event.clone());
284            event.finish_post_dispatch();
285        }
286        true
287    }
288
289    /// Presses the primary pointer button at the position set by `set_cursor`. Returns `true` when
290    /// a control or overlay handled the press.
291    pub fn pointer_pressed(&mut self) -> bool {
292        self.pointer_pressed_at_time(None)
293    }
294
295    /// Like [`pointer_pressed`](Self::pointer_pressed), but carries the
296    /// platform input timestamp (milliseconds) of the press sample.
297    pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
298        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
299        self.pointer_pressed_at_event_time(event_time)
300    }
301
302    /// Dispatch primary-button down with an already resolved event timestamp.
303    pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
304        let (cursor_x, cursor_y) = self.surface().cursor;
305        if self.inspector_press(cursor_x, cursor_y) {
306            return true;
307        }
308        if self.dev_overlay_press(cursor_x, cursor_y) {
309            return true;
310        }
311        let _event_handler = enter_event_handler_scope();
312        let result = {
313            let _app_context = self.shell.app.app_context.enter_scope();
314            run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
315        };
316        if result {
317            self.mark_dirty();
318        }
319        log::trace!(
320            target: "cranpose::input",
321            "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
322            event_time.platform_time_ms,
323            event_time.animation_time_nanos,
324        );
325        result
326    }
327
328    fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
329        self.activate();
330        self.note_focus_moved_by_keyboard(false);
331        self.surface_mut()
332            .buttons_pressed
333            .insert(PointerButton::Primary);
334
335        let (cursor_x, cursor_y) = self.surface().cursor;
336        let mut hits = self.surface().renderer.scene().hit_test(cursor_x, cursor_y);
337        if self.surface().pointer_source.is_touch_like()
338            && let Some(near) = self
339                .surface()
340                .renderer
341                .scene()
342                .hit_test_near(cursor_x, cursor_y)
343            && hits.iter().all(|hit| {
344                hit.node_id() != near.node_id() && near.capture_path().contains(&hit.node_id())
345            })
346        {
347            hits.insert(0, near);
348        }
349        let event = self.cursor_event(PointerEventKind::Down, event_time);
350        self.press_hits(hits, event, PointerId::PRIMARY)
351    }
352
353    /// An event of `kind` at the cursor, with the buttons held and the
354    /// pointer's source.
355    fn cursor_event(&self, kind: PointerEventKind, event_time: PointerEventTime) -> PointerEvent {
356        let (x, y) = self.surface().cursor;
357        self.pointer_event(kind, Point { x, y }, Point { x, y }, event_time)
358            .with_buttons(self.surface().buttons_pressed)
359            .with_source(self.surface().pointer_source)
360    }
361
362    /// Delivers a press to `hits`, nearest first, until one consumes it, and
363    /// keeps the paths it reached as `pointer`'s gesture targets.
364    fn press_hits(
365        &mut self,
366        hits: Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget>,
367        event: PointerEvent,
368        pointer: PointerId,
369    ) -> bool {
370        if hits.is_empty() {
371            self.surface_mut().hit_path_tracker.remove_path(pointer);
372            return false;
373        }
374        let mut delivered_capture_paths = Vec::new();
375        let (app, surface) = self.parts();
376        let mut applier = app.composition.applier_mut();
377        for hit in hits {
378            let node_id = hit.node_id();
379            delivered_capture_paths.push(hit.capture_path());
380            hit.dispatch_with_applier(&mut applier, event.clone());
381            log::trace!(
382                target: "cranpose::input",
383                "dispatch {:?} node={} consumed={} stop_on_consume=true",
384                event.kind,
385                node_id,
386                event.is_consumed(),
387            );
388            if event.is_consumed() {
389                break;
390            }
391        }
392
393        surface
394            .hit_path_tracker
395            .add_hit_path(pointer, delivered_capture_paths);
396        log::trace!(
397            target: "cranpose::input",
398            "press cached_hit_path={:?}",
399            surface.hit_path_tracker.get_path(pointer),
400        );
401        true
402    }
403
404    /// Presses `button` of the mouse at the position set by `set_cursor`.
405    /// The primary button presses as [`pointer_pressed`](Self::pointer_pressed)
406    /// does. Another button reaches the nodes under the pointer as a
407    /// [`PointerEventKind::ButtonDown`], or, while a button is already down,
408    /// the nodes that took its press; those nodes take the moves until the
409    /// last button goes up. Returns `true` when a node received the press.
410    pub fn mouse_button_pressed(&mut self, button: PointerButton) -> bool {
411        if button == PointerButton::Primary {
412            return self.pointer_pressed();
413        }
414        self.mouse_button_change(|surface, event_time| {
415            surface.other_button_pressed(button, event_time)
416        })
417    }
418
419    /// Releases `button` of the mouse. The primary button releases as
420    /// [`pointer_released`](Self::pointer_released) does; another reaches the
421    /// nodes that took the press as a [`PointerEventKind::ButtonUp`]. Returns
422    /// `true` when a node received the release.
423    pub fn mouse_button_released(&mut self, button: PointerButton) -> bool {
424        if button == PointerButton::Primary {
425            return self.pointer_released();
426        }
427        self.mouse_button_change(|surface, event_time| {
428            surface.other_button_released(button, event_time)
429        })
430    }
431
432    fn mouse_button_change(
433        &mut self,
434        change: impl FnOnce(&mut Self, PointerEventTime) -> bool,
435    ) -> bool {
436        let event_time = self.shell.app.realtime_pointer_event_time(None);
437        let _event_handler = enter_event_handler_scope();
438        let result = {
439            let _app_context = self.shell.app.app_context.enter_scope();
440            run_in_mutable_snapshot(|| change(self, event_time)).unwrap_or(false)
441        };
442        if result {
443            self.mark_dirty();
444        }
445        result
446    }
447
448    fn other_button_pressed(
449        &mut self,
450        button: PointerButton,
451        event_time: PointerEventTime,
452    ) -> bool {
453        self.activate();
454        let first = self.surface().buttons_pressed == PointerButtons::NONE;
455        self.surface_mut().buttons_pressed.insert(button);
456        let event = self.cursor_event(PointerEventKind::ButtonDown(button), event_time);
457        if first {
458            let (x, y) = self.surface().cursor;
459            let hits = self.surface().renderer.scene().hit_test(x, y);
460            return self.press_hits(hits, event, PointerId::PRIMARY);
461        }
462        let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
463        if targets.is_empty() {
464            return false;
465        }
466        self.dispatch_targets(targets, event, false);
467        true
468    }
469
470    fn other_button_released(
471        &mut self,
472        button: PointerButton,
473        event_time: PointerEventTime,
474    ) -> bool {
475        if !self.surface().buttons_pressed.contains(button) {
476            return false;
477        }
478        self.surface_mut().buttons_pressed.remove(button);
479        let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
480        if self.surface().buttons_pressed == PointerButtons::NONE {
481            self.surface_mut()
482                .hit_path_tracker
483                .remove_path(PointerId::PRIMARY);
484        }
485        if targets.is_empty() {
486            return false;
487        }
488        let event = self.cursor_event(PointerEventKind::ButtonUp(button), event_time);
489        self.dispatch_targets(targets, event, false);
490        true
491    }
492
493    /// Releases the primary pointer button at the position set by `set_cursor`. Returns `true` when
494    /// a control or overlay handled the release.
495    pub fn pointer_released(&mut self) -> bool {
496        self.pointer_released_at_time(None)
497    }
498
499    /// Releases the pointer at the position carried by the platform's release
500    /// sample (Android `ACTION_UP`, web `pointerup`/`touchend`).
501    ///
502    /// The cursor is moved to `(x, y)` WITHOUT dispatching a Move event, then
503    /// the Up event is dispatched at that position. Platforms whose release
504    /// events carry their own coordinates must use this instead of
505    /// `set_cursor* + pointer_released*`: lift-off samples routinely roll back
506    /// a few dp against the travel direction as the finger peels off, and
507    /// feeding that jitter into gesture velocity trackers as a final Move
508    /// sample can flip the sign of the computed fling velocity (flings that
509    /// suddenly go the opposite way). Jetpack Compose likewise never feeds the
510    /// up sample into velocity tracking.
511    pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
512        self.pointer_released_at_position_time(x, y, None)
513    }
514
515    /// Like [`pointer_released_at_position`](Self::pointer_released_at_position),
516    /// but carries the platform input timestamp (milliseconds) of the release
517    /// sample.
518    pub fn pointer_released_at_position_time(
519        &mut self,
520        x: f32,
521        y: f32,
522        time_ms: Option<i64>,
523    ) -> bool {
524        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
525        self.pointer_released_at_position_event_time(x, y, event_time)
526    }
527
528    /// Release at a position with an already resolved event timestamp.
529    pub fn pointer_released_at_position_event_time(
530        &mut self,
531        x: f32,
532        y: f32,
533        event_time: PointerEventTime,
534    ) -> bool {
535        let _event_handler = enter_event_handler_scope();
536        let result = {
537            let _app_context = self.shell.app.app_context.enter_scope();
538            run_in_mutable_snapshot(|| {
539                self.surface_mut().cursor = (x, y);
540                self.pointer_released_inner(event_time)
541            })
542            .unwrap_or(false)
543        };
544        self.route_drag_and_drop();
545        if result {
546            self.mark_dirty();
547        }
548        log::trace!(
549            target: "cranpose::input",
550            "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
551            event_time.platform_time_ms,
552            event_time.animation_time_nanos,
553        );
554        result
555    }
556
557    /// Like [`pointer_released`](Self::pointer_released), but carries the
558    /// platform input timestamp (milliseconds) of the release sample.
559    pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
560        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
561        self.pointer_released_at_event_time(event_time)
562    }
563
564    /// Dispatch primary-button up with an already resolved event timestamp.
565    pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
566        let _event_handler = enter_event_handler_scope();
567        let result = {
568            let _app_context = self.shell.app.app_context.enter_scope();
569            run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
570        };
571        self.route_drag_and_drop();
572        if result {
573            self.mark_dirty();
574        }
575        log::trace!(
576            target: "cranpose::input",
577            "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
578            event_time.platform_time_ms,
579            event_time.animation_time_nanos,
580        );
581        result
582    }
583
584    fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
585        if self.surface_mut().inspector.release_pointer() {
586            return true;
587        }
588        self.release_app_pointer(event_time)
589    }
590
591    fn release_app_pointer(&mut self, event_time: PointerEventTime) -> bool {
592        self.surface_mut()
593            .buttons_pressed
594            .remove(PointerButton::Primary);
595        let corrected_buttons = self.surface().buttons_pressed;
596        let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
597
598        // Another button still down keeps the gesture's targets for its
599        // moves and its release.
600        if corrected_buttons == PointerButtons::NONE {
601            self.surface_mut()
602                .hit_path_tracker
603                .remove_path(PointerId::PRIMARY);
604        }
605
606        if !targets.is_empty() {
607            let event = self
608                .pointer_event(
609                    PointerEventKind::Up,
610                    Point {
611                        x: self.surface().cursor.0,
612                        y: self.surface().cursor.1,
613                    },
614                    Point {
615                        x: self.surface().cursor.0,
616                        y: self.surface().cursor.1,
617                    },
618                    event_time,
619                )
620                .with_buttons(corrected_buttons)
621                .with_source(self.surface().pointer_source);
622
623            self.dispatch_targets(targets, event, false);
624            true
625        } else {
626            false
627        }
628    }
629
630    /// Dispatches an event for a secondary pointer (`pointer_id != 0`).
631    ///
632    /// Multi-touch gestures act on the element the first finger grabbed, so
633    /// secondary pointers are routed to the hit path captured by the primary
634    /// pointer's Down. They carry no hover/click semantics and are ignored
635    /// when no primary gesture is in progress.
636    ///
637    /// Returns `true` when the event was dispatched to at least one target.
638    pub fn secondary_pointer_pressed(
639        &mut self,
640        pointer_id: u64,
641        x: f32,
642        y: f32,
643        time_ms: Option<i64>,
644    ) -> bool {
645        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
646        self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
647    }
648
649    /// Move counterpart of [`secondary_pointer_pressed`](Self::secondary_pointer_pressed).
650    pub fn secondary_pointer_moved(
651        &mut self,
652        pointer_id: u64,
653        x: f32,
654        y: f32,
655        time_ms: Option<i64>,
656    ) -> bool {
657        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
658        self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
659    }
660
661    /// Release counterpart of [`secondary_pointer_pressed`](Self::secondary_pointer_pressed).
662    pub fn secondary_pointer_released(
663        &mut self,
664        pointer_id: u64,
665        x: f32,
666        y: f32,
667        time_ms: Option<i64>,
668    ) -> bool {
669        let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
670        self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
671    }
672
673    fn dispatch_secondary_pointer(
674        &mut self,
675        kind: PointerEventKind,
676        pointer_id: u64,
677        x: f32,
678        y: f32,
679        event_time: PointerEventTime,
680    ) -> bool {
681        if pointer_id == 0 {
682            log::warn!(
683                target: "cranpose::input",
684                "secondary pointer dispatch called with the primary pointer id"
685            );
686            return false;
687        }
688
689        let _event_handler = enter_event_handler_scope();
690        let result = {
691            let _app_context = self.shell.app.app_context.enter_scope();
692            run_in_mutable_snapshot(|| {
693                if !self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
694                    return false;
695                }
696                let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
697                if targets.is_empty() {
698                    return false;
699                }
700                let pos = Point { x, y };
701                let event = self
702                    .pointer_event(kind, pos, pos, event_time)
703                    .with_buttons(self.surface().buttons_pressed)
704                    .with_id(pointer_id)
705                    .with_source(self.surface().pointer_source);
706                self.dispatch_targets(targets, event, false);
707                true
708            })
709            .unwrap_or(false)
710        };
711        if result {
712            self.mark_dirty();
713        }
714        log::trace!(
715            target: "cranpose::input",
716            "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
717            event_time.platform_time_ms,
718            event_time.animation_time_nanos,
719        );
720        result
721    }
722
723    /// Points pointer `pointer_id` along `ray` in the 3D scene the node
724    /// `scene` shows: a controller, a hand or a gaze, in the coordinates of
725    /// the space the scene's eye stands in. A `Down` reaches the content of
726    /// the mesh surface the ray meets first and keeps its targets as the
727    /// pointer's gesture, which the pointer's `Move`s and its `Up` or
728    /// `Cancel` reach wherever the ray points then. `pointer_id`, not 0,
729    /// tells the pointer from the window's others. Returns `true` when a
730    /// target received the event.
731    pub fn ray_pointer(
732        &mut self,
733        (pointer_id, scene): (u32, NodeId),
734        ray: cranpose_ui_graphics::space::Ray,
735        kind: PointerEventKind,
736    ) -> bool {
737        if pointer_id == 0 {
738            log::warn!(
739                target: "cranpose::input",
740                "ray pointer dispatch called with the primary pointer id"
741            );
742            return false;
743        }
744        let event_time = self.shell.app.realtime_pointer_event_time(None);
745        let _event_handler = enter_event_handler_scope();
746        let result = {
747            let _app_context = self.shell.app.app_context.enter_scope();
748            run_in_mutable_snapshot(|| {
749                let pointer = PointerId(pointer_id);
750                let event = self
751                    .pointer_event(kind, Point::default(), Point::default(), event_time)
752                    .with_id(u64::from(pointer_id))
753                    .with_ray(ray);
754                if kind == PointerEventKind::Down {
755                    let hits = self.surface().renderer.scene().hit_test_ray(scene, ray);
756                    return self.press_hits(hits, event, pointer);
757                }
758                let targets = self.resolve_gesture_targets(pointer);
759                if matches!(kind, PointerEventKind::Up | PointerEventKind::Cancel) {
760                    self.surface_mut().hit_path_tracker.remove_path(pointer);
761                }
762                if targets.is_empty() {
763                    return false;
764                }
765                self.dispatch_targets(targets, event, false);
766                true
767            })
768            .unwrap_or(false)
769        };
770        if result {
771            self.mark_dirty();
772        }
773        result
774    }
775
776    /// Dispatches a discrete zoom step (desktop ctrl+wheel, browser pinch)
777    /// to the pointer handlers under the cursor.
778    ///
779    /// `zoom_factor` is multiplicative: `> 1.0` zooms in, `< 1.0` zooms out.
780    /// Returns `true` if a handler consumed the event.
781    pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
782        let event_time = self.shell.app.realtime_pointer_event_time(None);
783        let _event_handler = enter_event_handler_scope();
784        let result = {
785            let _app_context = self.shell.app.app_context.enter_scope();
786            run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
787                .unwrap_or(false)
788        };
789        if result {
790            self.mark_dirty();
791        }
792        log::trace!(
793            target: "cranpose::input",
794            "pointer_zoomed factor={zoom_factor:.4} -> {result}"
795        );
796        result
797    }
798
799    fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
800        if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
801            return false;
802        }
803
804        let hits = self
805            .surface()
806            .renderer
807            .scene()
808            .hit_test(self.surface().cursor.0, self.surface().cursor.1);
809        if hits.is_empty() {
810            return false;
811        }
812
813        let pos = Point {
814            x: self.surface().cursor.0,
815            y: self.surface().cursor.1,
816        };
817        let event = self
818            .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
819            .with_buttons(self.surface().buttons_pressed)
820            .with_zoom_delta(zoom_factor)
821            .with_source(self.surface().pointer_source);
822
823        self.dispatch_to_hits(&hits, event)
824    }
825
826    /// Dispatches one mouse-wheel / trackpad sample through the whole wheel
827    /// policy, and returns `true` when something consumed it.
828    ///
829    /// This is the single entry point every host with a wheel calls, after
830    /// placing the cursor. A wheel sample is not just a scroll — it is whichever
831    /// of four things the modifiers and the tree make it, in this order:
832    ///
833    /// 1. **Zoom** when ctrl is held. That is the desktop convention and the
834    ///    way browsers deliver a trackpad pinch, so both arrive here as the
835    ///    same gesture.
836    /// 2. **Rotary**, offered to [`rotary_scrolled`](Self::rotary_scrolled)
837    ///    before anything else can take it, so the Wear OS crown stack is
838    ///    developable on a machine with a wheel. Nothing consumes rotary unless
839    ///    the app opts in via `Modifier::on_rotary_scroll_event` or
840    ///    [`set_on_rotary_scroll`](Self::set_on_rotary_scroll), so ordinary
841    ///    scrolling is unaffected.
842    /// 3. **Horizontal scroll** when alt is held on a wheel that only reports a
843    ///    vertical axis.
844    /// 4. **Scroll**, to the hovered scrollable.
845    ///
846    /// Hosts must not re-implement this order. Doing so is how the browser
847    /// ended up scrolling backwards and never delivering rotary at all: the
848    /// policy lived in the desktop event loop, and the second host that grew a
849    /// wheel reimplemented the parts of it that were obvious from the outside.
850    pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
851        if self.inspector_scroll(wheel.delta.y) {
852            return true;
853        }
854        if wheel.is_zoom() {
855            let zoom_factor = wheel.zoom_factor();
856            log::trace!(
857                target: "cranpose::input",
858                "wheel zoom factor={zoom_factor:.4}"
859            );
860            return self.pointer_zoomed(zoom_factor);
861        }
862
863        let rotary =
864            RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
865        if self.rotary_scrolled(rotary) {
866            return true;
867        }
868
869        let delta = wheel.scroll_delta();
870        log::trace!(
871            target: "cranpose::input",
872            "wheel delta ({:.2},{:.2}) alt={}",
873            delta.x,
874            delta.y,
875            wheel.modifiers.alt
876        );
877        self.pointer_scrolled(delta.x, delta.y)
878    }
879
880    /// Dispatches a mouse wheel / trackpad scroll event to hovered pointer handlers.
881    ///
882    /// Returns `true` if a handler consumed the event.
883    ///
884    /// This is the last step of the wheel policy, not its entry point: hosts
885    /// call [`wheel_scrolled`](Self::wheel_scrolled), which reaches here once
886    /// zoom and rotary have declined the sample.
887    pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
888        if self.inspector_scroll(delta_y) {
889            return true;
890        }
891        let event_time = self.shell.app.realtime_pointer_event_time(None);
892        let _event_handler = enter_event_handler_scope();
893        let result = {
894            let _app_context = self.shell.app.app_context.enter_scope();
895            run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
896                .unwrap_or(false)
897        };
898        if result {
899            self.mark_dirty();
900        }
901        log::trace!(
902            target: "cranpose::input",
903            "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
904        );
905        result
906    }
907
908    fn pointer_scrolled_inner(
909        &mut self,
910        delta_x: f32,
911        delta_y: f32,
912        event_time: PointerEventTime,
913    ) -> bool {
914        if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
915            return false;
916        }
917
918        let hits = self
919            .surface()
920            .renderer
921            .scene()
922            .hit_test(self.surface().cursor.0, self.surface().cursor.1);
923        if hits.is_empty() {
924            return false;
925        }
926
927        let event = self
928            .pointer_event(
929                PointerEventKind::Scroll,
930                Point {
931                    x: self.surface().cursor.0,
932                    y: self.surface().cursor.1,
933                },
934                Point {
935                    x: self.surface().cursor.0,
936                    y: self.surface().cursor.1,
937                },
938                event_time,
939            )
940            .with_buttons(self.surface().buttons_pressed)
941            .with_scroll_delta(Point {
942                x: delta_x,
943                y: delta_y,
944            })
945            .with_source(self.surface().pointer_source);
946
947        self.dispatch_to_hits(&hits, event)
948    }
949
950    /// Installs the window-level rotary (Wear OS crown / rotating bezel)
951    /// handler — the low-level escape hatch.
952    ///
953    /// The handler runs only after the routed modifier chain has declined the
954    /// event (see [`rotary_scrolled`](Self::rotary_scrolled)), so an app that
955    /// draws everything into a single canvas receives every rotary delta
956    /// without registering a focus target or a modifier. Returning `true`
957    /// reports the event as consumed to the platform.
958    ///
959    /// Passing a new handler replaces the previous one.
960    pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
961    where
962        F: Fn(RotaryScrollEvent) -> bool + 'static,
963    {
964        self.surface_mut().on_rotary_scroll = Some(Rc::new(handler));
965    }
966
967    /// Removes the window-level rotary handler, if one is installed.
968    pub fn clear_on_rotary_scroll(&mut self) {
969        self.surface_mut().on_rotary_scroll = None;
970    }
971
972    /// Dispatches a rotary scroll expressed in raw detents (Android
973    /// `AXIS_SCROLL`), converting to pixels with the configured scroll factor.
974    ///
975    /// Applies Compose's sign convention: a positive detent value (crown turned
976    /// up/away) produces a negative `vertical_scroll_pixels`.
977    pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
978        let factor = self.shell.app.rotary_scroll_factor;
979        self.rotary_scrolled(RotaryScrollEvent::from_detents(
980            detents,
981            factor,
982            factor,
983            uptime_millis,
984        ))
985    }
986
987    /// Dispatches a rotary scroll event (Wear OS crown, Galaxy Watch bezel, or
988    /// a desktop mouse wheel standing in for one during development).
989    ///
990    /// Routing mirrors Compose's `RotaryInputModifierNode` contract:
991    ///
992    /// 1. Resolve the target chain. When a focus target is registered
993    ///    (`cranpose_ui::focus_dispatch::active_focus_target`) and still
994    ///    exists in the current scene, rotary uses the focus target's capture
995    ///    path. Otherwise, rotary uses the hit-test chain under the cursor.
996    /// 2. **Capture pass**, root to leaf, invoking `on_pre_rotary_scroll_event`
997    ///    handlers.
998    /// 3. **Bubble pass**, leaf to root, invoking `on_rotary_scroll_event`
999    ///    handlers.
1000    /// 4. If still unconsumed, the window-level handler installed by
1001    ///    [`set_on_rotary_scroll`](Self::set_on_rotary_scroll).
1002    ///
1003    /// The first handler returning `true` consumes the event and stops every
1004    /// remaining step. Returns `true` when the event was consumed.
1005    pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
1006        let _event_handler = enter_event_handler_scope();
1007        let result = {
1008            let _app_context = self.shell.app.app_context.enter_scope();
1009            run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
1010        };
1011        if result {
1012            self.mark_dirty();
1013        }
1014        log::trace!(
1015            target: "cranpose::input",
1016            "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
1017            event.vertical_scroll_pixels,
1018            event.horizontal_scroll_pixels,
1019            event.uptime_millis,
1020        );
1021        result
1022    }
1023
1024    fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
1025        if rotary.is_empty() {
1026            return false;
1027        }
1028
1029        let bubble_order = self.rotary_dispatch_order();
1030        let position = Point {
1031            x: self.surface().cursor.0,
1032            y: self.surface().cursor.1,
1033        };
1034
1035        if !bubble_order.is_empty() {
1036            let capture_targets = bubble_order
1037                .iter()
1038                .rev()
1039                .filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
1040                .collect::<Vec<_>>();
1041            let mut capture_event =
1042                PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
1043            capture_event.modifiers = self.shell.app.modifiers;
1044            self.dispatch_targets(capture_targets, capture_event.clone(), true);
1045            if capture_event.is_consumed() {
1046                return true;
1047            }
1048
1049            let bubble_targets = bubble_order
1050                .iter()
1051                .filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
1052                .collect::<Vec<_>>();
1053            let mut bubble_event =
1054                PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
1055            bubble_event.modifiers = self.shell.app.modifiers;
1056            self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
1057            if bubble_event.is_consumed() {
1058                return true;
1059            }
1060        }
1061
1062        if let Some(handler) = self.surface().on_rotary_scroll.clone() {
1063            return handler(rotary);
1064        }
1065
1066        false
1067    }
1068
1069    fn rotary_dispatch_order(&self) -> Vec<NodeId> {
1070        if let Some(focused) = cranpose_ui::active_focus_target()
1071            && let Some(target) = self.surface().renderer.scene().find_target(focused)
1072        {
1073            let path = target.capture_path();
1074            if !path.is_empty() {
1075                return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
1076            }
1077        }
1078
1079        let hits = self
1080            .surface()
1081            .renderer
1082            .scene()
1083            .hit_test(self.surface().cursor.0, self.surface().cursor.1);
1084        if hits.is_empty() {
1085            return Vec::new();
1086        }
1087        let capture_paths = hits
1088            .iter()
1089            .map(cranpose_render_common::HitTestTarget::capture_path)
1090            .collect::<Vec<_>>();
1091        crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
1092    }
1093
1094    /// Cancels any active gesture, dispatching Cancel events to cached targets.
1095    /// Call this when:
1096    /// - Window loses focus
1097    /// - Mouse leaves window while button pressed
1098    /// - Any other gesture abort scenario
1099    pub fn cancel_gesture(&mut self) {
1100        let event_time = self.shell.app.realtime_pointer_event_time(None);
1101        let _event_handler = enter_event_handler_scope();
1102        let _ = {
1103            let _app_context = self.shell.app.app_context.enter_scope();
1104            run_in_mutable_snapshot(|| {
1105                self.cancel_gesture_inner(event_time);
1106            })
1107        };
1108        self.route_drag_and_drop();
1109    }
1110
1111    /// Gives the held press to `node` alone, as a long press does once it
1112    /// fires: every handler under the press sees it cancelled, the other
1113    /// handlers on `node` too, so a button does not click when the press
1114    /// ends, and the rest of the press reaches `node` alone. Does nothing on
1115    /// a surface whose press does not reach `node`.
1116    pub(crate) fn give_press_to(&mut self, node: NodeId) {
1117        let holds = self
1118            .surface()
1119            .hit_path_tracker
1120            .get_path(PointerId::PRIMARY)
1121            .is_some_and(|paths| paths.iter().any(|path| path.contains(&node)));
1122        if !holds {
1123            return;
1124        }
1125        let event_time = self.shell.app.realtime_pointer_event_time(None);
1126        let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
1127        self.surface_mut()
1128            .hit_path_tracker
1129            .add_hit_path(PointerId::PRIMARY, vec![vec![node]]);
1130        let cursor = Point {
1131            x: self.surface().cursor.0,
1132            y: self.surface().cursor.1,
1133        };
1134        let event = self
1135            .pointer_event(PointerEventKind::Cancel, cursor, cursor, event_time)
1136            .with_source(self.surface().pointer_source);
1137        let _event_handler = enter_event_handler_scope();
1138        let _ = run_in_mutable_snapshot(|| self.dispatch_targets(targets, event, false));
1139    }
1140
1141    fn route_drag_and_drop(&mut self) {
1142        let source = self.index;
1143        let shell = &*self.shell;
1144        let app_context = &shell.app.app_context;
1145        let routed = app_context.enter(|| {
1146            run_in_mutable_snapshot(|| {
1147                app_context
1148                    .drag_and_drop()
1149                    .route(|point| shell.drag_and_drop_target_at(point, source))
1150            })
1151            .unwrap_or(false)
1152        });
1153        if routed {
1154            self.shell.mark_all_dirty();
1155        }
1156    }
1157
1158    /// Ends the gesture only when the pointer leaving really ended it.
1159    ///
1160    /// A held button belongs to the surface that received the press until
1161    /// the release: a window drawn over the cursor, or a drag carried past
1162    /// an edge, both leave the press where it started and deliver the
1163    /// release there too. Cancelling on either would drop a gesture the
1164    /// user has not finished, so a pressed pointer is left alone and only
1165    /// an idle one cancels.
1166    pub fn cancel_gesture_unless_pressed(&mut self) {
1167        if self.surface().buttons_pressed != PointerButtons::NONE
1168            || self.surface().inspector.pointer_captured
1169        {
1170            return;
1171        }
1172        self.cancel_gesture();
1173    }
1174
1175    fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
1176        self.surface_mut().inspector.cancel_pointer();
1177        let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
1178
1179        self.surface_mut().hit_path_tracker.clear();
1180        self.surface_mut().buttons_pressed = PointerButtons::NONE;
1181
1182        if !targets.is_empty() {
1183            let event = self
1184                .pointer_event(
1185                    PointerEventKind::Cancel,
1186                    Point {
1187                        x: self.surface().cursor.0,
1188                        y: self.surface().cursor.1,
1189                    },
1190                    Point {
1191                        x: self.surface().cursor.0,
1192                        y: self.surface().cursor.1,
1193                    },
1194                    event_time,
1195                )
1196                .with_source(self.surface().pointer_source);
1197
1198            self.dispatch_targets(targets, event, false);
1199        }
1200
1201        let pos = Point {
1202            x: self.surface().cursor.0,
1203            y: self.surface().cursor.1,
1204        };
1205        let hovered_nodes = self.surface().hovered_nodes.clone();
1206        for node_id in hovered_nodes {
1207            if let Some(target) = self.surface().renderer.scene().find_target(node_id) {
1208                let exit_event = self
1209                    .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
1210                    .with_source(self.surface().pointer_source);
1211                self.dispatch_targets(std::iter::once(target), exit_event, false);
1212            }
1213        }
1214        self.surface_mut().hovered_nodes.clear();
1215        self.surface().pointer_icon.set(PointerIcon::DEFAULT);
1216    }
1217
1218    fn apply_hovered_pointer_icon(
1219        &self,
1220        hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
1221    ) {
1222        let icon = hits
1223            .iter()
1224            .find_map(HitTestTarget::pointer_icon)
1225            .unwrap_or(PointerIcon::DEFAULT);
1226        self.surface().pointer_icon.set(icon);
1227    }
1228
1229    fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
1230        if !plain_key_down(event) || event.key_code != KeyCode::Tab {
1231            return false;
1232        }
1233        self.on_group_navigation_key(event)
1234    }
1235
1236    fn on_group_navigation_key(&mut self, event: &KeyEvent) -> bool {
1237        let focused = cranpose_ui::active_focus_target();
1238        let target = self.with_layout_tree(|tree| {
1239            cranpose_ui::keyboard_focus_target(
1240                tree?,
1241                focused,
1242                event.key_code,
1243                event.modifiers.shift,
1244            )
1245        });
1246        let Some(target) = target else {
1247            return false;
1248        };
1249        let moved =
1250            run_in_mutable_snapshot(|| cranpose_ui::request_focus_from_platform(target.node_id))
1251                .unwrap_or(false);
1252        if moved {
1253            self.mark_dirty();
1254            self.note_focus_moved_by_keyboard(true);
1255            if target.activate {
1256                self.on_activation_key(&KeyEvent::key_down(KeyCode::Space, " "));
1257            }
1258        }
1259        moved
1260    }
1261
1262    fn on_activation_key(&mut self, event: &KeyEvent) -> bool {
1263        if !plain_key_down(event)
1264            || !matches!(event.key_code, KeyCode::Enter | KeyCode::Space)
1265            || cranpose_ui::text_field_focus::has_focused_field()
1266        {
1267            return false;
1268        }
1269        let Some(focused) = cranpose_ui::active_focus_target() else {
1270            return false;
1271        };
1272        let activated = self.accessibility_activate(focused, None);
1273        self.note_focus_moved_by_keyboard(true);
1274        activated
1275    }
1276
1277    fn on_arrow_key(&mut self, event: &KeyEvent) -> bool {
1278        if !plain_key_down(event) || cranpose_ui::text_field_focus::has_focused_field() {
1279            return false;
1280        }
1281        if self.on_group_navigation_key(event) {
1282            return true;
1283        }
1284        let direction = match event.key_code {
1285            KeyCode::ArrowLeft => FocusDirection::Left,
1286            KeyCode::ArrowRight => FocusDirection::Right,
1287            KeyCode::ArrowUp => FocusDirection::Up,
1288            KeyCode::ArrowDown => FocusDirection::Down,
1289            _ => return false,
1290        };
1291        let Some(focused) = cranpose_ui::active_focus_target() else {
1292            return false;
1293        };
1294        let order = self.with_layout_tree(|layout_tree| {
1295            let layout_tree = layout_tree?;
1296            let group = cranpose_ui::selectable_group_of(layout_tree, focused)?;
1297            Some(cranpose_ui::collect_focus_order_under(layout_tree, group))
1298        });
1299        order.is_some_and(|order| self.move_focus_in_order(order, direction))
1300    }
1301
1302    fn note_focus_moved_by_keyboard(&mut self, keyboard: bool) {
1303        if cranpose_ui::set_keyboard_focus_visible(keyboard) {
1304            cranpose_ui::request_render_invalidation();
1305            self.mark_dirty();
1306        }
1307    }
1308
1309    fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
1310        if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
1311            return false;
1312        }
1313        self.dismiss_top_modal_in_context()
1314    }
1315
1316    /// Asks the innermost open dialog or popup to close, the way the platform
1317    /// back gesture does. Answers whether one was open to take the request.
1318    pub fn dismiss_top_modal(&mut self) -> bool {
1319        let _event_handler = enter_event_handler_scope();
1320        let _app_context = self.shell.app.app_context.enter_scope();
1321        self.dismiss_top_modal_in_context()
1322    }
1323
1324    fn dismiss_top_modal_in_context(&mut self) -> bool {
1325        let closed = run_in_mutable_snapshot(|| {
1326            if cranpose_ui::modal_depth() > 0 {
1327                cranpose_ui::dispatch_modal_back()
1328            } else {
1329                cranpose_ui::dismiss_top_popup()
1330            }
1331        })
1332        .unwrap_or(false);
1333        if closed {
1334            self.mark_dirty();
1335        }
1336        closed
1337    }
1338
1339    /// Publishes the focus order layout left behind and moves focus one step.
1340    /// Answers whether focus moved.
1341    pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
1342        let order = self.with_layout_tree(|layout_tree| {
1343            layout_tree
1344                .map(cranpose_ui::collect_focus_order)
1345                .unwrap_or_default()
1346        });
1347        self.move_focus_in_order(order, direction)
1348    }
1349
1350    fn move_focus_in_order(
1351        &mut self,
1352        order: Vec<cranpose_ui::FocusEntry>,
1353        direction: FocusDirection,
1354    ) -> bool {
1355        cranpose_ui::set_focus_order(order);
1356        let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
1357            .unwrap_or(false);
1358        if moved {
1359            self.mark_dirty();
1360            self.note_focus_moved_by_keyboard(true);
1361        }
1362        moved
1363    }
1364
1365    /// Sends a key event to the focused control, the key handlers above it and the focus traversal.
1366    /// Returns `true` when the event was handled and the platform should not process it further.
1367    pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1368        let _event_handler = enter_event_handler_scope();
1369        let _app_context = self.shell.app.app_context.enter_scope();
1370        let handled = self.on_key_event_inner(event);
1371        self.run_requested_focus_move();
1372        handled
1373    }
1374
1375    /// Makes the focus move a keyboard action asked for during the event.
1376    fn run_requested_focus_move(&mut self) {
1377        if let Some(direction) = cranpose_ui::take_requested_focus_move() {
1378            self.move_focus_in_context(direction);
1379        }
1380    }
1381
1382    fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1383        if self.inspector_key(event) {
1384            return true;
1385        }
1386        let root = self.key_event_root();
1387        if root.is_none() && self.id() != RootId::Primary {
1388            return false;
1389        }
1390        let route =
1391            cranpose_ui::KeyEventRoute::new(&mut self.shell.app.composition.applier_mut(), root);
1392        if self.dispatch_modifier_key(&route, event, true) {
1393            return true;
1394        }
1395        if route.focus_is_current() && self.on_focused_key_event(event) {
1396            return true;
1397        }
1398        if self.dispatch_modifier_key(&route, event, false) {
1399            return true;
1400        }
1401        if !route.focus_is_current() || cranpose_ui::text_field_focus::has_focused_field() {
1402            return false;
1403        }
1404        let handled = run_in_mutable_snapshot(|| cranpose_ui::dispatch_unhandled_key_event(event))
1405            .unwrap_or(false);
1406        if handled {
1407            self.mark_dirty();
1408            self.shell.app.request_layout_pass();
1409        }
1410        handled
1411    }
1412
1413    fn dispatch_modifier_key(
1414        &mut self,
1415        route: &cranpose_ui::KeyEventRoute,
1416        event: &KeyEvent,
1417        preview: bool,
1418    ) -> bool {
1419        let handled = run_in_mutable_snapshot(|| {
1420            let mut applier = self.shell.app.composition.applier_mut();
1421            if preview {
1422                route.dispatch_preview(&mut applier, event)
1423            } else {
1424                route.dispatch_bubble(&mut applier, event)
1425            }
1426        })
1427        .unwrap_or(false);
1428        if handled {
1429            self.mark_dirty();
1430            self.shell.app.request_layout_pass();
1431        }
1432        handled
1433    }
1434
1435    fn key_event_root(&self) -> Option<cranpose_core::NodeId> {
1436        self.surface()
1437            .modal_focus
1438            .last()
1439            .map(|entry| entry.0)
1440            .or_else(|| self.surface().root)
1441            .or_else(|| match self.id() {
1442                RootId::Primary => self.shell.app.composition.root(),
1443                RootId::Window(_) => None,
1444            })
1445    }
1446
1447    fn receives_focused_input(&mut self) -> bool {
1448        let root = self.key_event_root();
1449        cranpose_ui::KeyEventRoute::contains_focus(
1450            &mut self.shell.app.composition.applier_mut(),
1451            root,
1452        )
1453    }
1454
1455    fn on_focused_key_event(&mut self, event: &KeyEvent) -> bool {
1456        use KeyEventType::KeyDown;
1457
1458        if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1459            #[cfg(all(
1460                feature = "clipboard-native",
1461                not(target_arch = "wasm32"),
1462                not(target_os = "android"),
1463                not(target_os = "ios")
1464            ))]
1465            {
1466                match event.key_code {
1467                    KeyCode::C => {
1468                        if let Some(text) = self.on_copy_inner() {
1469                            cranpose_ui::clipboard_session::clipboard_write_text(&text);
1470                            return true;
1471                        }
1472                    }
1473                    KeyCode::X => {
1474                        if let Some(text) = self.on_cut_inner() {
1475                            cranpose_ui::clipboard_session::clipboard_write_text(&text);
1476                            self.mark_dirty();
1477                            self.shell.app.request_layout_pass();
1478                            return true;
1479                        }
1480                    }
1481                    KeyCode::V => {
1482                        if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1483                            && self.on_paste_inner(&text)
1484                        {
1485                            return true;
1486                        }
1487                    }
1488                    _ => {}
1489                }
1490            }
1491        }
1492
1493        if self.on_focus_key(event) {
1494            return true;
1495        }
1496
1497        if self.on_escape_key(event) {
1498            return true;
1499        }
1500
1501        if self.on_activation_key(event) || self.on_arrow_key(event) {
1502            return true;
1503        }
1504
1505        let handled =
1506            run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1507                .unwrap_or(false);
1508
1509        if handled {
1510            self.mark_dirty();
1511            self.shell.app.request_layout_pass();
1512        }
1513
1514        handled
1515    }
1516
1517    /// Handles paste event from platform clipboard.
1518    /// Returns `true` if a focused text field in this surface consumed the paste.
1519    pub fn on_paste(&mut self, text: &str) -> bool {
1520        if self.inspector_owns_keyboard() {
1521            return true;
1522        }
1523        let _event_handler = enter_event_handler_scope();
1524        let _app_context = self.shell.app.app_context.enter_scope();
1525        self.on_paste_inner(text)
1526    }
1527
1528    fn on_paste_inner(&mut self, text: &str) -> bool {
1529        if !self.receives_focused_input() {
1530            return false;
1531        }
1532        let handled =
1533            run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1534                .unwrap_or(false);
1535
1536        if handled {
1537            self.mark_dirty();
1538            self.shell.app.request_layout_pass();
1539        }
1540
1541        handled
1542    }
1543
1544    /// Handles copy request from platform.
1545    /// Returns the selected text from this surface's focused field, or `None`.
1546    pub fn on_copy(&mut self) -> Option<String> {
1547        if self.inspector_owns_keyboard() {
1548            return None;
1549        }
1550        let _app_context = self.shell.app.app_context.enter_scope();
1551        self.on_copy_inner()
1552    }
1553
1554    fn on_copy_inner(&mut self) -> Option<String> {
1555        if !self.receives_focused_input() {
1556            return None;
1557        }
1558        cranpose_ui::text_field_focus::dispatch_copy()
1559    }
1560
1561    /// Handles cut request from platform.
1562    /// Returns the cut text from this surface's focused field, or `None`.
1563    pub fn on_cut(&mut self) -> Option<String> {
1564        let _event_handler = enter_event_handler_scope();
1565        if self.inspector_owns_keyboard() {
1566            return None;
1567        }
1568        let _app_context = self.shell.app.app_context.enter_scope();
1569        self.on_cut_inner()
1570    }
1571
1572    fn on_cut_inner(&mut self) -> Option<String> {
1573        if !self.receives_focused_input() {
1574            return None;
1575        }
1576        let text =
1577            run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1578
1579        if text.is_some() {
1580            self.mark_dirty();
1581            self.shell.app.request_layout_pass();
1582        }
1583
1584        text
1585    }
1586
1587    /// Handles IME preedit (composition) events.
1588    /// Called when the input method is composing text (e.g., typing CJK characters).
1589    ///
1590    /// - `text`: The current preedit text (empty to clear composition state)
1591    /// - `cursor`: Optional cursor position within the preedit text (start, end)
1592    ///
1593    /// Returns `true` if a text field consumed the event.
1594    pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1595        if self.inspector_owns_keyboard() {
1596            return true;
1597        }
1598        let _event_handler = enter_event_handler_scope();
1599        let _app_context = self.shell.app.app_context.enter_scope();
1600        self.on_ime_preedit_inner(text, cursor)
1601    }
1602
1603    fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1604        if !self.receives_focused_input() {
1605            return false;
1606        }
1607        let handled = run_in_mutable_snapshot(|| {
1608            cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1609        })
1610        .unwrap_or(false);
1611
1612        if handled {
1613            self.mark_dirty();
1614            self.shell.app.request_layout_pass();
1615        }
1616
1617        handled
1618    }
1619
1620    /// Finishes the active IME composition, keeping the composed text as
1621    /// committed text (Android `finishComposingText` semantics).
1622    /// Returns `true` if a text field consumed the event.
1623    pub fn on_ime_finish_composing(&mut self) -> bool {
1624        if self.inspector_owns_keyboard() {
1625            return true;
1626        }
1627        let _event_handler = enter_event_handler_scope();
1628        let _app_context = self.shell.app.app_context.enter_scope();
1629        self.on_ime_finish_composing_inner()
1630    }
1631
1632    fn on_ime_finish_composing_inner(&mut self) -> bool {
1633        if !self.receives_focused_input() {
1634            return false;
1635        }
1636        let handled =
1637            run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1638                .unwrap_or(false);
1639
1640        if handled {
1641            self.mark_dirty();
1642            self.shell.app.request_layout_pass();
1643        }
1644
1645        handled
1646    }
1647
1648    /// Runs the focused field's keyboard action, as the software keyboard's
1649    /// action key asks: Android's editor action, iOS's return key, the web
1650    /// editor's Enter. [`ImeAction::Default`](cranpose_ui::ImeAction::Default)
1651    /// stands for the field's own action. Returns `true` if a text field
1652    /// handled it.
1653    pub fn on_ime_action(&mut self, action: cranpose_ui::ImeAction) -> bool {
1654        if self.inspector_owns_keyboard() {
1655            return true;
1656        }
1657        let _event_handler = enter_event_handler_scope();
1658        let _app_context = self.shell.app.app_context.enter_scope();
1659        if !self.receives_focused_input() {
1660            return false;
1661        }
1662        let handled =
1663            run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_ime_action(action))
1664                .unwrap_or(false);
1665        if handled {
1666            self.mark_dirty();
1667            self.shell.app.request_layout_pass();
1668        }
1669        self.run_requested_focus_move();
1670        handled
1671    }
1672
1673    /// Marks existing text in the focused field as the composing region
1674    /// without changing it (Android `setComposingRegion` semantics). Offsets
1675    /// are UTF-8 bytes. Returns `true` if a text field consumed the event.
1676    pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1677        if self.inspector_owns_keyboard() {
1678            return true;
1679        }
1680        let _event_handler = enter_event_handler_scope();
1681        let _app_context = self.shell.app.app_context.enter_scope();
1682        if !self.receives_focused_input() {
1683            return false;
1684        }
1685        let handled = run_in_mutable_snapshot(|| {
1686            cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(start_bytes, end_bytes)
1687        })
1688        .unwrap_or(false);
1689
1690        if handled {
1691            self.mark_dirty();
1692            self.shell.app.request_layout_pass();
1693        }
1694
1695        handled
1696    }
1697
1698    /// Moves the focused field's selection/caret to `[start_bytes, end_bytes)`
1699    /// without editing text (Android `InputConnection.setSelection`; the path
1700    /// Gboard's spacebar-swipe uses to scrub the cursor). Offsets are UTF-8
1701    /// bytes. Returns `true` if a text field consumed the event.
1702    pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1703        if self.inspector_owns_keyboard() {
1704            return true;
1705        }
1706        let _event_handler = enter_event_handler_scope();
1707        let _app_context = self.shell.app.app_context.enter_scope();
1708        if !self.receives_focused_input() {
1709            return false;
1710        }
1711        let handled = run_in_mutable_snapshot(|| {
1712            cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1713        })
1714        .unwrap_or(false);
1715
1716        if handled {
1717            self.mark_dirty();
1718        }
1719
1720        handled
1721    }
1722
1723    /// Returns a snapshot of the focused text field's editable state for
1724    /// platform IMEs (text, selection and composition in UTF-8 bytes), or
1725    /// `None` when this surface has no focused text field.
1726    pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1727        let _app_context = self.shell.app.app_context.enter_scope();
1728        self.receives_focused_input()
1729            .then(cranpose_ui::text_field_focus::focused_editor_state)
1730            .flatten()
1731    }
1732
1733    /// Window-space caret geometry of the focused field for coordinate-based
1734    /// platform text input (iOS trackpad cursor + tap-to-position), or `None`
1735    /// when this surface has no focused text field.
1736    pub fn ime_caret_geometry(
1737        &mut self,
1738    ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1739        let _app_context = self.shell.app.app_context.enter_scope();
1740        self.receives_focused_input()
1741            .then(cranpose_ui::text_field_focus::focused_caret_geometry)
1742            .flatten()
1743    }
1744
1745    /// Window bounds of the focused field in logical pixels, for platforms
1746    /// that place a native editor over it, or `None` when this surface has
1747    /// no focused text field that has been laid out.
1748    pub fn ime_field_bounds(&mut self) -> Option<cranpose_ui_graphics::Rect> {
1749        let _app_context = self.shell.app.app_context.enter_scope();
1750        self.receives_focused_input()
1751            .then(cranpose_ui::text_field_focus::focused_field_bounds)
1752            .flatten()
1753    }
1754
1755    /// Whether the focused text field holds a secret, as
1756    /// `Modifier::password()` marks one. Only the semantics of the field's
1757    /// own node are read, so no semantics tree is built, and the answer holds
1758    /// with semantics off. A platform gives such a field a protected editor,
1759    /// which a software keyboard neither learns from nor suggests for.
1760    /// `false` when this surface has no focused text field.
1761    pub fn ime_field_is_password(&mut self) -> bool {
1762        let _app_context = self.shell.app.app_context.enter_scope();
1763        let Some(field) = self
1764            .receives_focused_input()
1765            .then(cranpose_ui::text_field_focus::focused_field_target)
1766            .flatten()
1767        else {
1768            return false;
1769        };
1770        let mut applier = self.shell.app.composition.applier_mut();
1771        applier
1772            .with_node::<LayoutNode, _>(field, |node| node.semantics_configuration())
1773            .or_else(|_| {
1774                applier.with_node::<SubcomposeLayoutNode, _>(field, |node| {
1775                    node.semantics_configuration()
1776                })
1777            })
1778            .ok()
1779            .flatten()
1780            .is_some_and(|config| config.password)
1781    }
1782
1783    /// Clears text-field focus (used by platform IME actions such as
1784    /// Android's Done). The focus-loss notification hides the soft keyboard.
1785    pub fn clear_text_field_focus(&mut self) {
1786        let _event_handler = enter_event_handler_scope();
1787        let _app_context = self.shell.app.app_context.enter_scope();
1788        if self.receives_focused_input() {
1789            cranpose_ui::text_field_focus::clear_focus();
1790            self.mark_dirty();
1791            self.shell.app.request_layout_pass();
1792        }
1793    }
1794
1795    /// Handles IME delete-surrounding events.
1796    /// Returns `true` if a text field consumed the event.
1797    pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1798        if self.inspector_owns_keyboard() {
1799            return true;
1800        }
1801        let _event_handler = enter_event_handler_scope();
1802        let _app_context = self.shell.app.app_context.enter_scope();
1803        self.on_ime_delete_surrounding_inner(before_bytes, after_bytes)
1804    }
1805
1806    fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1807        if !self.receives_focused_input() {
1808            return false;
1809        }
1810        let handled = run_in_mutable_snapshot(|| {
1811            cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1812        })
1813        .unwrap_or(false);
1814
1815        if handled {
1816            self.mark_dirty();
1817            self.shell.app.request_layout_pass();
1818        }
1819
1820        handled
1821    }
1822}
1823
1824impl<R> AppShell<R>
1825where
1826    R: Renderer,
1827    R::Error: Debug,
1828{
1829    /// Sets the keyboard modifiers held right now, so the platform's live
1830    /// modifier state (winit's `ModifiersChanged`, a DOM event's
1831    /// `shiftKey`/`ctrlKey`/`altKey`/`metaKey`) reaches every `PointerEvent`
1832    /// the shell dispatches from here on -- the same state the wheel path
1833    /// already carries via [`WheelScroll::with_modifiers`](crate::WheelScroll::with_modifiers).
1834    /// A platform that never calls this leaves pointer events reporting
1835    /// `None` (see [`PointerEvent::modifiers`]) rather than a silently wrong
1836    /// "nothing held".
1837    pub fn set_modifiers(&mut self, modifiers: Modifiers) {
1838        self.app.modifiers = Some(modifiers);
1839    }
1840
1841    /// The keyboard modifiers most recently set via
1842    /// [`set_modifiers`](Self::set_modifiers), or `None` if the platform has
1843    /// never reported them.
1844    pub fn modifiers(&self) -> Option<Modifiers> {
1845        self.app.modifiers
1846    }
1847
1848    /// Pixels per rotary detent used by
1849    /// [`rotary_scrolled_by_detents`](Self::rotary_scrolled_by_detents).
1850    pub fn rotary_scroll_factor(&self) -> f32 {
1851        self.app.rotary_scroll_factor
1852    }
1853
1854    /// Sets the pixels-per-detent factor for rotary input.
1855    ///
1856    /// On Wear OS this must be `ViewConfiguration.getScaledVerticalScrollFactor()`
1857    /// for pixel-exact parity with Compose. The host activity can read it over
1858    /// JNI once at startup and push it here; when it does not, the shell falls
1859    /// back to [`DEFAULT_ROTARY_SCROLL_FACTOR_DP`] scaled by display density.
1860    ///
1861    /// Non-finite or non-positive values are ignored.
1862    pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
1863        if factor.is_finite() && factor > 0.0 {
1864            self.app.rotary_scroll_factor = factor;
1865        }
1866    }
1867
1868    /// Notifies the framework that the host app was paused/backgrounded.
1869    ///
1870    /// Withdraws any outstanding soft-keyboard request (and hides the keyboard)
1871    /// so the "keyboard shown" state does not survive across the pause and get
1872    /// restored on resume with no focused field. Platform runtimes call this
1873    /// from their pause lifecycle event.
1874    pub fn notify_app_paused(&mut self) {
1875        self.app
1876            .app_context
1877            .enter(cranpose_ui::text_input_session::notify_app_paused);
1878    }
1879
1880    /// Notifies the framework that the host app resumed/foregrounded.
1881    ///
1882    /// Never auto-shows the soft keyboard, even for a still-focused field: a
1883    /// warm resume keeps the caret but must not resurrect the keyboard (the user
1884    /// taps the field to bring it back). Always returns `false` so the platform
1885    /// runtime force-hides the OS-restored keyboard. Platform runtimes call this
1886    /// from their resume lifecycle event.
1887    pub fn notify_app_resumed(&mut self) -> bool {
1888        self.app
1889            .app_context
1890            .enter(cranpose_ui::text_input_session::notify_app_resumed)
1891    }
1892
1893    /// Copies `text` to the Linux PRIMARY selection, which a middle click pastes.
1894    #[cfg(all(
1895        feature = "clipboard-native",
1896        target_os = "linux",
1897        not(target_arch = "wasm32")
1898    ))]
1899    pub fn set_primary_selection(&mut self, text: &str) {
1900        use arboard::{LinuxClipboardKind, SetExtLinux};
1901        if let Some(ref mut clipboard) = self.app.clipboard {
1902            let result = clipboard
1903                .set()
1904                .clipboard(LinuxClipboardKind::Primary)
1905                .text(text.to_string());
1906            if let Err(e) = result {
1907                log::debug!("Primary selection set failed: {e:?}");
1908            }
1909        }
1910    }
1911
1912    /// Copies `text` to the Linux PRIMARY selection, which a middle click pastes. Does nothing
1913    /// here, because this build has no PRIMARY selection.
1914    #[cfg(not(all(
1915        feature = "clipboard-native",
1916        target_os = "linux",
1917        not(target_arch = "wasm32")
1918    )))]
1919    pub fn set_primary_selection(&mut self, _text: &str) {}
1920
1921    /// Returns the text in the Linux PRIMARY selection, or `None` when it is empty or unavailable.
1922    #[cfg(all(
1923        feature = "clipboard-native",
1924        target_os = "linux",
1925        not(target_arch = "wasm32")
1926    ))]
1927    pub fn get_primary_selection(&mut self) -> Option<String> {
1928        use arboard::{GetExtLinux, LinuxClipboardKind};
1929        if let Some(ref mut clipboard) = self.app.clipboard {
1930            clipboard
1931                .get()
1932                .clipboard(LinuxClipboardKind::Primary)
1933                .text()
1934                .ok()
1935        } else {
1936            None
1937        }
1938    }
1939
1940    /// Returns `None` always, because this build has no PRIMARY selection.
1941    #[cfg(not(all(
1942        feature = "clipboard-native",
1943        target_os = "linux",
1944        not(target_arch = "wasm32")
1945    )))]
1946    pub fn get_primary_selection(&mut self) -> Option<String> {
1947        None
1948    }
1949
1950    /// Syncs the current text field selection to PRIMARY (Linux X11).
1951    /// Call this when selection changes in a text field.
1952    pub fn sync_selection_to_primary(&mut self) {
1953        #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1954        {
1955            if let Some(text) = self.on_copy() {
1956                self.set_primary_selection(&text);
1957            }
1958        }
1959    }
1960
1961    /// Primary-surface form of [`SurfaceMut::set_pointer_source`].
1962    pub fn set_pointer_source(&mut self, source: PointerSource) {
1963        self.primary().set_pointer_source(source);
1964    }
1965
1966    /// Primary-surface form of [`SurfaceMut::pointer_source`].
1967    pub fn pointer_source(&self) -> PointerSource {
1968        self.surfaces[0].pointer_source
1969    }
1970
1971    /// Primary-surface form of [`SurfaceMut::set_cursor`].
1972    pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
1973        self.primary().set_cursor(x, y)
1974    }
1975
1976    /// Primary-surface form of [`SurfaceMut::set_cursor_at_time`].
1977    pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
1978        self.primary().set_cursor_at_time(x, y, time_ms)
1979    }
1980
1981    /// Primary-surface form of [`SurfaceMut::set_cursor_at_event_time`].
1982    pub fn set_cursor_at_event_time(
1983        &mut self,
1984        x: f32,
1985        y: f32,
1986        event_time: PointerEventTime,
1987    ) -> bool {
1988        self.primary().set_cursor_at_event_time(x, y, event_time)
1989    }
1990
1991    /// Primary-surface form of [`SurfaceMut::pointer_pressed`].
1992    pub fn pointer_pressed(&mut self) -> bool {
1993        self.primary().pointer_pressed()
1994    }
1995
1996    /// Primary-surface form of [`SurfaceMut::accessibility_activate`].
1997    pub fn accessibility_activate(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> bool {
1998        self.primary().accessibility_activate(node_id, canvas_key)
1999    }
2000
2001    /// Primary-surface form of [`SurfaceMut::pointer_pressed_at_time`].
2002    pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
2003        self.primary().pointer_pressed_at_time(time_ms)
2004    }
2005
2006    /// Primary-surface form of [`SurfaceMut::pointer_pressed_at_event_time`].
2007    pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
2008        self.primary().pointer_pressed_at_event_time(event_time)
2009    }
2010
2011    /// Primary-surface form of [`SurfaceMut::pointer_released`].
2012    pub fn pointer_released(&mut self) -> bool {
2013        self.primary().pointer_released()
2014    }
2015
2016    /// Primary-surface form of [`SurfaceMut::pointer_released_at_position`].
2017    pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
2018        self.primary().pointer_released_at_position(x, y)
2019    }
2020
2021    /// Primary-surface form of
2022    /// [`SurfaceMut::pointer_released_at_position_time`].
2023    pub fn pointer_released_at_position_time(
2024        &mut self,
2025        x: f32,
2026        y: f32,
2027        time_ms: Option<i64>,
2028    ) -> bool {
2029        self.primary()
2030            .pointer_released_at_position_time(x, y, time_ms)
2031    }
2032
2033    /// Primary-surface form of
2034    /// [`SurfaceMut::pointer_released_at_position_event_time`].
2035    pub fn pointer_released_at_position_event_time(
2036        &mut self,
2037        x: f32,
2038        y: f32,
2039        event_time: PointerEventTime,
2040    ) -> bool {
2041        self.primary()
2042            .pointer_released_at_position_event_time(x, y, event_time)
2043    }
2044
2045    /// Primary-surface form of [`SurfaceMut::pointer_released_at_time`].
2046    pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
2047        self.primary().pointer_released_at_time(time_ms)
2048    }
2049
2050    /// Primary-surface form of [`SurfaceMut::pointer_released_at_event_time`].
2051    pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
2052        self.primary().pointer_released_at_event_time(event_time)
2053    }
2054
2055    /// Primary-surface form of [`SurfaceMut::secondary_pointer_pressed`].
2056    pub fn secondary_pointer_pressed(
2057        &mut self,
2058        pointer_id: u64,
2059        x: f32,
2060        y: f32,
2061        time_ms: Option<i64>,
2062    ) -> bool {
2063        self.primary()
2064            .secondary_pointer_pressed(pointer_id, x, y, time_ms)
2065    }
2066
2067    /// Primary-surface form of [`SurfaceMut::ray_pointer`].
2068    pub fn ray_pointer(
2069        &mut self,
2070        (pointer_id, scene): (u32, NodeId),
2071        ray: cranpose_ui_graphics::space::Ray,
2072        kind: PointerEventKind,
2073    ) -> bool {
2074        self.primary().ray_pointer((pointer_id, scene), ray, kind)
2075    }
2076
2077    /// Primary-surface form of [`SurfaceMut::secondary_pointer_moved`].
2078    pub fn secondary_pointer_moved(
2079        &mut self,
2080        pointer_id: u64,
2081        x: f32,
2082        y: f32,
2083        time_ms: Option<i64>,
2084    ) -> bool {
2085        self.primary()
2086            .secondary_pointer_moved(pointer_id, x, y, time_ms)
2087    }
2088
2089    /// Primary-surface form of [`SurfaceMut::secondary_pointer_released`].
2090    pub fn secondary_pointer_released(
2091        &mut self,
2092        pointer_id: u64,
2093        x: f32,
2094        y: f32,
2095        time_ms: Option<i64>,
2096    ) -> bool {
2097        self.primary()
2098            .secondary_pointer_released(pointer_id, x, y, time_ms)
2099    }
2100
2101    /// Primary-surface form of [`SurfaceMut::pointer_zoomed`].
2102    pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
2103        self.primary().pointer_zoomed(zoom_factor)
2104    }
2105
2106    /// Primary-surface form of [`SurfaceMut::wheel_scrolled`].
2107    pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
2108        self.primary().wheel_scrolled(wheel)
2109    }
2110
2111    /// Primary-surface form of [`SurfaceMut::pointer_scrolled`].
2112    pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
2113        self.primary().pointer_scrolled(delta_x, delta_y)
2114    }
2115
2116    /// Primary-surface form of [`SurfaceMut::set_on_rotary_scroll`].
2117    pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
2118    where
2119        F: Fn(RotaryScrollEvent) -> bool + 'static,
2120    {
2121        self.primary().set_on_rotary_scroll(handler);
2122    }
2123
2124    /// Primary-surface form of [`SurfaceMut::clear_on_rotary_scroll`].
2125    pub fn clear_on_rotary_scroll(&mut self) {
2126        self.primary().clear_on_rotary_scroll();
2127    }
2128
2129    /// Primary-surface form of [`SurfaceMut::rotary_scrolled_by_detents`].
2130    pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
2131        self.primary()
2132            .rotary_scrolled_by_detents(detents, uptime_millis)
2133    }
2134
2135    /// Primary-surface form of [`SurfaceMut::rotary_scrolled`].
2136    pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
2137        self.primary().rotary_scrolled(event)
2138    }
2139
2140    /// Primary-surface form of [`SurfaceMut::cancel_gesture`].
2141    pub fn cancel_gesture(&mut self) {
2142        self.primary().cancel_gesture();
2143    }
2144
2145    /// Primary-surface form of [`SurfaceMut::cancel_gesture_unless_pressed`].
2146    pub fn cancel_gesture_unless_pressed(&mut self) {
2147        self.primary().cancel_gesture_unless_pressed();
2148    }
2149
2150    /// Offers the primary window's current pointer icon to the platform
2151    /// again, for the moments a windowing system has drawn its own default
2152    /// over it.
2153    pub fn refresh_pointer_icon(&self) {
2154        self.surfaces[0].pointer_icon.refresh();
2155    }
2156
2157    /// The pointer icon the platform has not applied to the primary window
2158    /// yet, or `None` when the icon has not changed since the last call.
2159    ///
2160    /// Platform backends call this after handing the shell a batch of input and
2161    /// set the returned icon on the window they own. Platforms with no pointing
2162    /// device never call it.
2163    pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
2164        self.surfaces[0].pointer_icon.take_change()
2165    }
2166
2167    /// Installs the platform soft-keyboard handler for the primary window.
2168    ///
2169    /// The handler is invoked when a text field gains focus (`show_keyboard`)
2170    /// or when text-field focus is cleared or goes stale (`hide_keyboard`).
2171    /// Platform runtimes with an on-screen keyboard (Android, iOS) call this
2172    /// once after creating the shell.
2173    pub fn set_platform_text_input(
2174        &mut self,
2175        handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
2176    ) {
2177        self.primary().set_platform_text_input(handler);
2178    }
2179
2180    /// Primary-surface form of [`SurfaceMut::dismiss_top_modal`].
2181    pub fn dismiss_top_modal(&mut self) -> bool {
2182        self.primary().dismiss_top_modal()
2183    }
2184
2185    /// Primary-surface form of [`SurfaceMut::move_focus_in_context`].
2186    pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
2187        self.primary().move_focus_in_context(direction)
2188    }
2189
2190    /// Primary-surface form of [`SurfaceMut::on_key_event`].
2191    pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
2192        self.primary().on_key_event(event)
2193    }
2194
2195    /// Primary-surface form of [`SurfaceMut::on_paste`].
2196    pub fn on_paste(&mut self, text: &str) -> bool {
2197        self.primary().on_paste(text)
2198    }
2199
2200    /// Primary-surface form of [`SurfaceMut::on_copy`].
2201    pub fn on_copy(&mut self) -> Option<String> {
2202        self.primary().on_copy()
2203    }
2204
2205    /// Primary-surface form of [`SurfaceMut::on_cut`].
2206    pub fn on_cut(&mut self) -> Option<String> {
2207        self.primary().on_cut()
2208    }
2209
2210    /// Primary-surface form of [`SurfaceMut::on_ime_preedit`].
2211    pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
2212        self.primary().on_ime_preedit(text, cursor)
2213    }
2214
2215    /// Primary-surface form of [`SurfaceMut::on_ime_finish_composing`].
2216    pub fn on_ime_finish_composing(&mut self) -> bool {
2217        self.primary().on_ime_finish_composing()
2218    }
2219
2220    /// Primary-surface form of [`SurfaceMut::on_ime_action`].
2221    pub fn on_ime_action(&mut self, action: cranpose_ui::ImeAction) -> bool {
2222        self.primary().on_ime_action(action)
2223    }
2224
2225    /// Primary-surface form of [`SurfaceMut::on_ime_set_composing_region`].
2226    pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
2227        self.primary()
2228            .on_ime_set_composing_region(start_bytes, end_bytes)
2229    }
2230
2231    /// Primary-surface form of [`SurfaceMut::on_ime_set_selection`].
2232    pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
2233        self.primary().on_ime_set_selection(start_bytes, end_bytes)
2234    }
2235
2236    /// Primary-surface form of [`SurfaceMut::ime_editor_state`].
2237    pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
2238        self.primary().ime_editor_state()
2239    }
2240
2241    /// Primary-surface form of [`SurfaceMut::ime_caret_geometry`].
2242    pub fn ime_caret_geometry(
2243        &mut self,
2244    ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
2245        self.primary().ime_caret_geometry()
2246    }
2247
2248    /// Primary-surface form of [`SurfaceMut::ime_field_bounds`].
2249    pub fn ime_field_bounds(&mut self) -> Option<cranpose_ui_graphics::Rect> {
2250        self.primary().ime_field_bounds()
2251    }
2252
2253    /// Primary-surface form of [`SurfaceMut::ime_field_is_password`].
2254    pub fn ime_field_is_password(&mut self) -> bool {
2255        self.primary().ime_field_is_password()
2256    }
2257
2258    /// Primary-surface form of [`SurfaceMut::clear_text_field_focus`].
2259    pub fn clear_text_field_focus(&mut self) {
2260        self.primary().clear_text_field_focus();
2261    }
2262
2263    /// Primary-surface form of [`SurfaceMut::on_ime_delete_surrounding`].
2264    pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
2265        self.primary()
2266            .on_ime_delete_surrounding(before_bytes, after_bytes)
2267    }
2268}
2269
2270enum ActivationTarget {
2271    Direct(cranpose_foundation::SemanticsCustomAction),
2272    Edit(NodeId),
2273    Pointer(NodeId, Option<Rect>),
2274}
2275
2276fn activation_target(
2277    node: &cranpose_ui::SemanticsNode,
2278    node_id: NodeId,
2279    canvas_key: Option<u64>,
2280) -> Option<ActivationTarget> {
2281    if node.hidden {
2282        return None;
2283    }
2284    if node.node_id != node_id {
2285        return node
2286            .children
2287            .iter()
2288            .find_map(|child| activation_target(child, node_id, canvas_key));
2289    }
2290    if !node.enabled {
2291        return None;
2292    }
2293    if let Some(key) = canvas_key {
2294        let child = node
2295            .details()
2296            .canvas_children
2297            .iter()
2298            .find(|child| child.key == key)?;
2299        return (child.enabled && child.clickable)
2300            .then_some(ActivationTarget::Pointer(node_id, Some(child.bounds)));
2301    }
2302    if let Some(action) = &node.on_click {
2303        return Some(ActivationTarget::Direct(action.clone()));
2304    }
2305    if node.details().editable_text && node.focusable {
2306        return Some(ActivationTarget::Edit(node_id));
2307    }
2308    node.actions.first().map(|action| match action {
2309        cranpose_ui::SemanticsAction::Click { handler } => {
2310            ActivationTarget::Pointer(handler.node_id(), None)
2311        }
2312    })
2313}
2314
2315fn activation_input(
2316    root: &LayoutBox,
2317    node_id: NodeId,
2318    canvas_bounds: Option<Rect>,
2319) -> Option<(Point, Point, Rc<cranpose_ui::ModifierNodeSlices>)> {
2320    if root.node_id != node_id {
2321        return root
2322            .children
2323            .iter()
2324            .find_map(|child| activation_input(child, node_id, canvas_bounds));
2325    }
2326    let bounds = canvas_bounds.map_or(root.rect, |bounds| Rect {
2327        x: root.rect.x + bounds.x,
2328        y: root.rect.y + bounds.y,
2329        ..bounds
2330    });
2331    let position = Point {
2332        x: bounds.x + bounds.width * 0.5,
2333        y: bounds.y + bounds.height * 0.5,
2334    };
2335    let local = Point {
2336        x: position.x - root.rect.x,
2337        y: position.y - root.rect.y,
2338    };
2339    (bounds.width > 0.0 && bounds.height > 0.0 && position.x.is_finite() && position.y.is_finite())
2340        .then(|| (position, local, Rc::clone(&root.node_data.modifier_slices)))
2341}
2342
2343fn plain_key_down(event: &KeyEvent) -> bool {
2344    event.event_type == KeyEventType::KeyDown
2345        && !event.modifiers.ctrl
2346        && !event.modifiers.meta
2347        && !event.modifiers.alt
2348}