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