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