Skip to main content

cranpose_app_shell/
shell_input.rs

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