1use cranpose_ui::FocusDirection;
2
3use super::*;
4
5impl<R> SurfaceMut<'_, R>
6where
7 R: Renderer,
8 R::Error: Debug,
9{
10 fn pointer_event(
11 &self,
12 kind: PointerEventKind,
13 position: Point,
14 global_position: Point,
15 event_time: PointerEventTime,
16 ) -> PointerEvent {
17 let screen_position = self.surface().screen_origin.map(|origin| Point {
18 x: origin.x + global_position.x,
19 y: origin.y + global_position.y,
20 });
21 let mut event = PointerEvent::new(kind, position, global_position)
22 .with_time_ms(event_time.platform_time_ms)
23 .with_animation_time_nanos(event_time.animation_time_nanos)
24 .with_screen_position(screen_position);
25 event.modifiers = self.shell.app.modifiers;
26 event
27 }
28
29 fn resolve_gesture_targets(
30 &self,
31 pointer: PointerId,
32 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
33 self.resolve_hit_path(pointer)
34 }
35
36 fn resolve_hit_path(
37 &self,
38 pointer: PointerId,
39 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
40 let Some(node_ids) = self.surface().hit_path_tracker.dispatch_order(pointer) else {
41 return Vec::new();
42 };
43
44 let scene = self.surface().renderer.scene();
45 let targets: Vec<_> = node_ids
46 .iter()
47 .filter_map(|&id| scene.find_target(id))
48 .collect();
49 log::trace!(
50 target: "cranpose::input",
51 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
52 targets.len()
53 );
54 targets
55 }
56
57 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
58 where
59 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
60 {
61 let mut applier = self.shell.app.composition.applier_mut();
62 for target in targets {
63 let node_id = target.node_id();
64 target.dispatch_with_applier(&mut applier, event.clone());
65 log::trace!(
66 target: "cranpose::input",
67 "dispatch {:?} node={} consumed={} stop_on_consume={}",
68 event.kind,
69 node_id,
70 event.is_consumed(),
71 stop_on_consume,
72 );
73 if stop_on_consume && event.is_consumed() {
74 break;
75 }
76 }
77 event.finish_post_dispatch();
78 }
79
80 fn dispatch_to_hits(
81 &mut self,
82 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
83 event: PointerEvent,
84 ) -> bool {
85 let capture_paths = hits
86 .iter()
87 .map(cranpose_render_common::HitTestTarget::capture_path)
88 .collect::<Vec<_>>();
89 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
90 .into_iter()
91 .filter_map(|node_id| self.surface().renderer.scene().find_target(node_id))
92 .collect::<Vec<_>>();
93
94 self.dispatch_targets(targets, event.clone(), true);
95
96 event.is_consumed()
97 }
98
99 pub fn set_pointer_source(&mut self, source: PointerSource) {
105 self.surface_mut().pointer_source = source;
106 }
107
108 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 {
116 self.set_cursor_at_time(x, y, None)
117 }
118
119 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
126 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
127 self.set_cursor_at_event_time(x, y, event_time)
128 }
129
130 pub fn set_cursor_at_event_time(
132 &mut self,
133 x: f32,
134 y: f32,
135 event_time: PointerEventTime,
136 ) -> bool {
137 let _event_handler = enter_event_handler_scope();
138 let app_context = Rc::clone(&self.shell.app.app_context);
139 let result = app_context.enter(|| {
140 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
141 });
142 self.route_drag_and_drop();
143 if result {
144 self.mark_dirty();
145 }
146 log::trace!(
147 target: "cranpose::input",
148 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
149 event_time.platform_time_ms,
150 event_time.animation_time_nanos,
151 );
152 result
153 }
154
155 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
156 self.surface_mut().cursor = (x, y);
157 if self.inspector_move(x, y) {
158 return true;
159 }
160
161 if self.surface().buttons_pressed != PointerButtons::NONE {
162 if self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
163 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
164 if !targets.is_empty() {
165 let event = self
166 .pointer_event(
167 PointerEventKind::Move,
168 Point { x, y },
169 Point { x, y },
170 event_time,
171 )
172 .with_buttons(self.surface().buttons_pressed)
173 .with_source(self.surface().pointer_source);
174 self.dispatch_targets(targets, event, false);
175 return true;
176 }
177
178 return false;
179 }
180
181 return false;
182 }
183
184 let hits = if self.inspector_blocks_pointer(x, y) {
185 Vec::new()
186 } else {
187 self.surface().renderer.scene().hit_test(x, y)
188 };
189 let new_ids: Vec<NodeId> = hits
190 .iter()
191 .map(cranpose_render_common::HitTestTarget::node_id)
192 .collect();
193
194 let pos = Point { x, y };
195 let previously_hovered = self.surface().hovered_nodes.clone();
196 for old_id in previously_hovered {
197 if !new_ids.contains(&old_id)
198 && let Some(target) = self.surface().renderer.scene().find_target(old_id)
199 {
200 let exit_event = self
201 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
202 .with_buttons(self.surface().buttons_pressed)
203 .with_source(self.surface().pointer_source);
204 self.dispatch_targets(std::iter::once(target), exit_event, false);
205 }
206 }
207
208 for hit in &hits {
209 if !self.surface().hovered_nodes.contains(&hit.node_id()) {
210 let enter_event = self
211 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
212 .with_buttons(self.surface().buttons_pressed)
213 .with_source(self.surface().pointer_source);
214 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
215 }
216 }
217
218 self.surface_mut().hovered_nodes = new_ids;
219 self.apply_hovered_pointer_icon(&hits);
220
221 if !hits.is_empty() {
222 let event = self
223 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
224 .with_buttons(self.surface().buttons_pressed)
225 .with_source(self.surface().pointer_source);
226 self.dispatch_targets(hits, event, true);
227 true
228 } else {
229 false
230 }
231 }
232
233 pub fn accessibility_activate(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> bool {
239 let event_time = self.shell.app.realtime_pointer_event_time(None);
240 let _event_handler = enter_event_handler_scope();
241 let app_context = Rc::clone(&self.shell.app.app_context);
242 let result = app_context.enter(|| {
243 run_in_mutable_snapshot(|| self.activate_node(node_id, canvas_key, event_time))
244 .unwrap_or(false)
245 });
246 if result {
247 self.mark_dirty();
248 }
249 result
250 }
251
252 fn activate_node(
253 &mut self,
254 node_id: NodeId,
255 canvas_key: Option<u64>,
256 event_time: PointerEventTime,
257 ) -> bool {
258 let target = {
259 let (app, surface) = self.parts();
260 surface
261 .semantics_tree_for_input(app)
262 .and_then(|tree| activation_target(tree.root(), node_id, canvas_key))
263 };
264 let (target_id, canvas_bounds) = match target {
265 Some(ActivationTarget::Direct(action)) => {
266 action.invoke();
267 return true;
268 }
269 Some(ActivationTarget::Edit(node_id)) => {
270 self.activate();
271 return cranpose_ui::request_focus_from_platform(node_id);
272 }
273 Some(ActivationTarget::Pointer(node_id, bounds)) => (node_id, bounds),
274 None => return false,
275 };
276 let position =
277 self.with_layout_tree(|tree| activation_input(tree?.root(), target_id, canvas_bounds));
278 let Some((position, local, handlers)) = position else {
279 return false;
280 };
281 for kind in [PointerEventKind::Down, PointerEventKind::Up] {
282 let event = self.pointer_event(kind, local, position, event_time);
283 handlers.dispatch_pointer_event(event.clone());
284 event.finish_post_dispatch();
285 }
286 true
287 }
288
289 pub fn pointer_pressed(&mut self) -> bool {
292 self.pointer_pressed_at_time(None)
293 }
294
295 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
298 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
299 self.pointer_pressed_at_event_time(event_time)
300 }
301
302 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
304 let (cursor_x, cursor_y) = self.surface().cursor;
305 if self.inspector_press(cursor_x, cursor_y) {
306 return true;
307 }
308 if self.dev_overlay_press(cursor_x, cursor_y) {
309 return true;
310 }
311 let _event_handler = enter_event_handler_scope();
312 let app_context = Rc::clone(&self.shell.app.app_context);
313 let result = app_context.enter(|| {
314 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
315 });
316 if result {
317 self.mark_dirty();
318 }
319 log::trace!(
320 target: "cranpose::input",
321 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
322 event_time.platform_time_ms,
323 event_time.animation_time_nanos,
324 );
325 result
326 }
327
328 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
329 self.activate();
330 self.note_focus_moved_by_keyboard(false);
331 self.surface_mut()
332 .buttons_pressed
333 .insert(PointerButton::Primary);
334
335 let (cursor_x, cursor_y) = self.surface().cursor;
336 let mut hits = self.surface().renderer.scene().hit_test(cursor_x, cursor_y);
337 if self.surface().pointer_source.is_touch_like()
338 && let Some(near) = self
339 .surface()
340 .renderer
341 .scene()
342 .hit_test_near(cursor_x, cursor_y)
343 && hits.iter().all(|hit| {
344 hit.node_id() != near.node_id() && near.capture_path().contains(&hit.node_id())
345 })
346 {
347 hits.insert(0, near);
348 }
349 if hits.is_empty() {
350 self.surface_mut()
351 .hit_path_tracker
352 .remove_path(PointerId::PRIMARY);
353 false
354 } else {
355 let event = self
356 .pointer_event(
357 PointerEventKind::Down,
358 Point {
359 x: self.surface().cursor.0,
360 y: self.surface().cursor.1,
361 },
362 Point {
363 x: self.surface().cursor.0,
364 y: self.surface().cursor.1,
365 },
366 event_time,
367 )
368 .with_buttons(self.surface().buttons_pressed)
369 .with_source(self.surface().pointer_source);
370
371 let mut delivered_capture_paths = Vec::new();
372 let (app, surface) = self.parts();
373 let mut applier = app.composition.applier_mut();
374 for hit in hits {
375 let node_id = hit.node_id();
376 delivered_capture_paths.push(hit.capture_path());
377 hit.dispatch_with_applier(&mut applier, event.clone());
378 log::trace!(
379 target: "cranpose::input",
380 "dispatch {:?} node={} consumed={} stop_on_consume=true",
381 event.kind,
382 node_id,
383 event.is_consumed(),
384 );
385 if event.is_consumed() {
386 break;
387 }
388 }
389
390 surface
391 .hit_path_tracker
392 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
393 log::trace!(
394 target: "cranpose::input",
395 "pointer_pressed_inner cached_hit_path={:?}",
396 surface.hit_path_tracker.get_path(PointerId::PRIMARY),
397 );
398
399 true
400 }
401 }
402
403 pub fn pointer_released(&mut self) -> bool {
406 self.pointer_released_at_time(None)
407 }
408
409 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
422 self.pointer_released_at_position_time(x, y, None)
423 }
424
425 pub fn pointer_released_at_position_time(
429 &mut self,
430 x: f32,
431 y: f32,
432 time_ms: Option<i64>,
433 ) -> bool {
434 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
435 self.pointer_released_at_position_event_time(x, y, event_time)
436 }
437
438 pub fn pointer_released_at_position_event_time(
440 &mut self,
441 x: f32,
442 y: f32,
443 event_time: PointerEventTime,
444 ) -> bool {
445 let _event_handler = enter_event_handler_scope();
446 let app_context = Rc::clone(&self.shell.app.app_context);
447 let result = app_context.enter(|| {
448 run_in_mutable_snapshot(|| {
449 self.surface_mut().cursor = (x, y);
450 self.pointer_released_inner(event_time)
451 })
452 .unwrap_or(false)
453 });
454 self.route_drag_and_drop();
455 if result {
456 self.mark_dirty();
457 }
458 log::trace!(
459 target: "cranpose::input",
460 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
461 event_time.platform_time_ms,
462 event_time.animation_time_nanos,
463 );
464 result
465 }
466
467 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
470 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
471 self.pointer_released_at_event_time(event_time)
472 }
473
474 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
476 let _event_handler = enter_event_handler_scope();
477 let app_context = Rc::clone(&self.shell.app.app_context);
478 let result = app_context.enter(|| {
479 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
480 });
481 self.route_drag_and_drop();
482 if result {
483 self.mark_dirty();
484 }
485 log::trace!(
486 target: "cranpose::input",
487 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
488 event_time.platform_time_ms,
489 event_time.animation_time_nanos,
490 );
491 result
492 }
493
494 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
495 if self.surface_mut().inspector.release_pointer() {
496 return true;
497 }
498 self.release_app_pointer(event_time)
499 }
500
501 fn release_app_pointer(&mut self, event_time: PointerEventTime) -> bool {
502 self.surface_mut()
503 .buttons_pressed
504 .remove(PointerButton::Primary);
505 let corrected_buttons = self.surface().buttons_pressed;
506 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
507
508 self.surface_mut()
509 .hit_path_tracker
510 .remove_path(PointerId::PRIMARY);
511
512 if !targets.is_empty() {
513 let event = self
514 .pointer_event(
515 PointerEventKind::Up,
516 Point {
517 x: self.surface().cursor.0,
518 y: self.surface().cursor.1,
519 },
520 Point {
521 x: self.surface().cursor.0,
522 y: self.surface().cursor.1,
523 },
524 event_time,
525 )
526 .with_buttons(corrected_buttons)
527 .with_source(self.surface().pointer_source);
528
529 self.dispatch_targets(targets, event, false);
530 true
531 } else {
532 false
533 }
534 }
535
536 pub fn secondary_pointer_pressed(
545 &mut self,
546 pointer_id: u64,
547 x: f32,
548 y: f32,
549 time_ms: Option<i64>,
550 ) -> bool {
551 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
552 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
553 }
554
555 pub fn secondary_pointer_moved(
557 &mut self,
558 pointer_id: u64,
559 x: f32,
560 y: f32,
561 time_ms: Option<i64>,
562 ) -> bool {
563 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
564 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
565 }
566
567 pub fn secondary_pointer_released(
569 &mut self,
570 pointer_id: u64,
571 x: f32,
572 y: f32,
573 time_ms: Option<i64>,
574 ) -> bool {
575 let event_time = self.shell.app.realtime_pointer_event_time(time_ms);
576 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
577 }
578
579 fn dispatch_secondary_pointer(
580 &mut self,
581 kind: PointerEventKind,
582 pointer_id: u64,
583 x: f32,
584 y: f32,
585 event_time: PointerEventTime,
586 ) -> bool {
587 if pointer_id == 0 {
588 log::warn!(
589 target: "cranpose::input",
590 "secondary pointer dispatch called with the primary pointer id"
591 );
592 return false;
593 }
594
595 let _event_handler = enter_event_handler_scope();
596 let app_context = Rc::clone(&self.shell.app.app_context);
597 let result = app_context.enter(|| {
598 run_in_mutable_snapshot(|| {
599 if !self.surface().hit_path_tracker.has_path(PointerId::PRIMARY) {
600 return false;
601 }
602 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
603 if targets.is_empty() {
604 return false;
605 }
606 let pos = Point { x, y };
607 let event = self
608 .pointer_event(kind, pos, pos, event_time)
609 .with_buttons(self.surface().buttons_pressed)
610 .with_id(pointer_id)
611 .with_source(self.surface().pointer_source);
612 self.dispatch_targets(targets, event, false);
613 true
614 })
615 .unwrap_or(false)
616 });
617 if result {
618 self.mark_dirty();
619 }
620 log::trace!(
621 target: "cranpose::input",
622 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
623 event_time.platform_time_ms,
624 event_time.animation_time_nanos,
625 );
626 result
627 }
628
629 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
635 let event_time = self.shell.app.realtime_pointer_event_time(None);
636 let _event_handler = enter_event_handler_scope();
637 let app_context = Rc::clone(&self.shell.app.app_context);
638 let result = app_context.enter(|| {
639 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
640 .unwrap_or(false)
641 });
642 if result {
643 self.mark_dirty();
644 }
645 log::trace!(
646 target: "cranpose::input",
647 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
648 );
649 result
650 }
651
652 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
653 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
654 return false;
655 }
656
657 let hits = self
658 .surface()
659 .renderer
660 .scene()
661 .hit_test(self.surface().cursor.0, self.surface().cursor.1);
662 if hits.is_empty() {
663 return false;
664 }
665
666 let pos = Point {
667 x: self.surface().cursor.0,
668 y: self.surface().cursor.1,
669 };
670 let event = self
671 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
672 .with_buttons(self.surface().buttons_pressed)
673 .with_zoom_delta(zoom_factor)
674 .with_source(self.surface().pointer_source);
675
676 self.dispatch_to_hits(&hits, event)
677 }
678
679 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
704 if self.inspector_scroll(wheel.delta.y) {
705 return true;
706 }
707 if wheel.is_zoom() {
708 let zoom_factor = wheel.zoom_factor();
709 log::trace!(
710 target: "cranpose::input",
711 "wheel zoom factor={zoom_factor:.4}"
712 );
713 return self.pointer_zoomed(zoom_factor);
714 }
715
716 let rotary =
717 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
718 if self.rotary_scrolled(rotary) {
719 return true;
720 }
721
722 let delta = wheel.scroll_delta();
723 log::trace!(
724 target: "cranpose::input",
725 "wheel delta ({:.2},{:.2}) alt={}",
726 delta.x,
727 delta.y,
728 wheel.modifiers.alt
729 );
730 self.pointer_scrolled(delta.x, delta.y)
731 }
732
733 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
741 if self.inspector_scroll(delta_y) {
742 return true;
743 }
744 let event_time = self.shell.app.realtime_pointer_event_time(None);
745 let _event_handler = enter_event_handler_scope();
746 let app_context = Rc::clone(&self.shell.app.app_context);
747 let result = app_context.enter(|| {
748 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
749 .unwrap_or(false)
750 });
751 if result {
752 self.mark_dirty();
753 }
754 log::trace!(
755 target: "cranpose::input",
756 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
757 );
758 result
759 }
760
761 fn pointer_scrolled_inner(
762 &mut self,
763 delta_x: f32,
764 delta_y: f32,
765 event_time: PointerEventTime,
766 ) -> bool {
767 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
768 return false;
769 }
770
771 let hits = self
772 .surface()
773 .renderer
774 .scene()
775 .hit_test(self.surface().cursor.0, self.surface().cursor.1);
776 if hits.is_empty() {
777 return false;
778 }
779
780 let event = self
781 .pointer_event(
782 PointerEventKind::Scroll,
783 Point {
784 x: self.surface().cursor.0,
785 y: self.surface().cursor.1,
786 },
787 Point {
788 x: self.surface().cursor.0,
789 y: self.surface().cursor.1,
790 },
791 event_time,
792 )
793 .with_buttons(self.surface().buttons_pressed)
794 .with_scroll_delta(Point {
795 x: delta_x,
796 y: delta_y,
797 })
798 .with_source(self.surface().pointer_source);
799
800 self.dispatch_to_hits(&hits, event)
801 }
802
803 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
814 where
815 F: Fn(RotaryScrollEvent) -> bool + 'static,
816 {
817 self.surface_mut().on_rotary_scroll = Some(Rc::new(handler));
818 }
819
820 pub fn clear_on_rotary_scroll(&mut self) {
822 self.surface_mut().on_rotary_scroll = None;
823 }
824
825 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
831 let factor = self.shell.app.rotary_scroll_factor;
832 self.rotary_scrolled(RotaryScrollEvent::from_detents(
833 detents,
834 factor,
835 factor,
836 uptime_millis,
837 ))
838 }
839
840 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
859 let _event_handler = enter_event_handler_scope();
860 let app_context = Rc::clone(&self.shell.app.app_context);
861 let result = app_context.enter(|| {
862 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
863 });
864 if result {
865 self.mark_dirty();
866 }
867 log::trace!(
868 target: "cranpose::input",
869 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
870 event.vertical_scroll_pixels,
871 event.horizontal_scroll_pixels,
872 event.uptime_millis,
873 );
874 result
875 }
876
877 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
878 if rotary.is_empty() {
879 return false;
880 }
881
882 let bubble_order = self.rotary_dispatch_order();
883 let position = Point {
884 x: self.surface().cursor.0,
885 y: self.surface().cursor.1,
886 };
887
888 if !bubble_order.is_empty() {
889 let capture_targets = bubble_order
890 .iter()
891 .rev()
892 .filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
893 .collect::<Vec<_>>();
894 let mut capture_event =
895 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
896 capture_event.modifiers = self.shell.app.modifiers;
897 self.dispatch_targets(capture_targets, capture_event.clone(), true);
898 if capture_event.is_consumed() {
899 return true;
900 }
901
902 let bubble_targets = bubble_order
903 .iter()
904 .filter_map(|&node_id| self.surface().renderer.scene().find_target(node_id))
905 .collect::<Vec<_>>();
906 let mut bubble_event =
907 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
908 bubble_event.modifiers = self.shell.app.modifiers;
909 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
910 if bubble_event.is_consumed() {
911 return true;
912 }
913 }
914
915 if let Some(handler) = self.surface().on_rotary_scroll.clone() {
916 return handler(rotary);
917 }
918
919 false
920 }
921
922 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
923 if let Some(focused) = cranpose_ui::active_focus_target()
924 && let Some(target) = self.surface().renderer.scene().find_target(focused)
925 {
926 let path = target.capture_path();
927 if !path.is_empty() {
928 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
929 }
930 }
931
932 let hits = self
933 .surface()
934 .renderer
935 .scene()
936 .hit_test(self.surface().cursor.0, self.surface().cursor.1);
937 if hits.is_empty() {
938 return Vec::new();
939 }
940 let capture_paths = hits
941 .iter()
942 .map(cranpose_render_common::HitTestTarget::capture_path)
943 .collect::<Vec<_>>();
944 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
945 }
946
947 pub fn cancel_gesture(&mut self) {
953 let event_time = self.shell.app.realtime_pointer_event_time(None);
954 let _event_handler = enter_event_handler_scope();
955 let app_context = Rc::clone(&self.shell.app.app_context);
956 let _ = app_context.enter(|| {
957 run_in_mutable_snapshot(|| {
958 self.cancel_gesture_inner(event_time);
959 })
960 });
961 self.route_drag_and_drop();
962 }
963
964 pub(crate) fn give_press_to(&mut self, node: NodeId) {
970 let holds = self
971 .surface()
972 .hit_path_tracker
973 .get_path(PointerId::PRIMARY)
974 .is_some_and(|paths| paths.iter().any(|path| path.contains(&node)));
975 if !holds {
976 return;
977 }
978 let event_time = self.shell.app.realtime_pointer_event_time(None);
979 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
980 self.surface_mut()
981 .hit_path_tracker
982 .add_hit_path(PointerId::PRIMARY, vec![vec![node]]);
983 let cursor = Point {
984 x: self.surface().cursor.0,
985 y: self.surface().cursor.1,
986 };
987 let event = self
988 .pointer_event(PointerEventKind::Cancel, cursor, cursor, event_time)
989 .with_source(self.surface().pointer_source);
990 let _event_handler = enter_event_handler_scope();
991 let _ = run_in_mutable_snapshot(|| self.dispatch_targets(targets, event, false));
992 }
993
994 fn route_drag_and_drop(&mut self) {
995 let app_context = Rc::clone(&self.shell.app.app_context);
996 let source = self.index;
997 let shell = &mut *self.shell;
998 let routed = app_context.enter(|| {
999 run_in_mutable_snapshot(|| {
1000 app_context
1001 .drag_and_drop()
1002 .route(|point| shell.drag_and_drop_target_at(point, source))
1003 })
1004 .unwrap_or(false)
1005 });
1006 if routed {
1007 shell.mark_all_dirty();
1008 }
1009 }
1010
1011 pub fn cancel_gesture_unless_pressed(&mut self) {
1020 if self.surface().buttons_pressed != PointerButtons::NONE
1021 || self.surface().inspector.pointer_captured
1022 {
1023 return;
1024 }
1025 self.cancel_gesture();
1026 }
1027
1028 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
1029 self.surface_mut().inspector.cancel_pointer();
1030 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
1031
1032 self.surface_mut().hit_path_tracker.clear();
1033 self.surface_mut().buttons_pressed = PointerButtons::NONE;
1034
1035 if !targets.is_empty() {
1036 let event = self
1037 .pointer_event(
1038 PointerEventKind::Cancel,
1039 Point {
1040 x: self.surface().cursor.0,
1041 y: self.surface().cursor.1,
1042 },
1043 Point {
1044 x: self.surface().cursor.0,
1045 y: self.surface().cursor.1,
1046 },
1047 event_time,
1048 )
1049 .with_source(self.surface().pointer_source);
1050
1051 self.dispatch_targets(targets, event, false);
1052 }
1053
1054 let pos = Point {
1055 x: self.surface().cursor.0,
1056 y: self.surface().cursor.1,
1057 };
1058 let hovered_nodes = self.surface().hovered_nodes.clone();
1059 for node_id in hovered_nodes {
1060 if let Some(target) = self.surface().renderer.scene().find_target(node_id) {
1061 let exit_event = self
1062 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
1063 .with_source(self.surface().pointer_source);
1064 self.dispatch_targets(std::iter::once(target), exit_event, false);
1065 }
1066 }
1067 self.surface_mut().hovered_nodes.clear();
1068 self.surface().pointer_icon.set(PointerIcon::DEFAULT);
1069 }
1070
1071 fn apply_hovered_pointer_icon(
1072 &self,
1073 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
1074 ) {
1075 let icon = hits
1076 .iter()
1077 .find_map(HitTestTarget::pointer_icon)
1078 .unwrap_or(PointerIcon::DEFAULT);
1079 self.surface().pointer_icon.set(icon);
1080 }
1081
1082 fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
1083 if !plain_key_down(event) || event.key_code != KeyCode::Tab {
1084 return false;
1085 }
1086 self.on_group_navigation_key(event)
1087 }
1088
1089 fn on_group_navigation_key(&mut self, event: &KeyEvent) -> bool {
1090 let focused = cranpose_ui::active_focus_target();
1091 let target = self.with_layout_tree(|tree| {
1092 cranpose_ui::keyboard_focus_target(
1093 tree?,
1094 focused,
1095 event.key_code,
1096 event.modifiers.shift,
1097 )
1098 });
1099 let Some(target) = target else {
1100 return false;
1101 };
1102 let moved =
1103 run_in_mutable_snapshot(|| cranpose_ui::request_focus_from_platform(target.node_id))
1104 .unwrap_or(false);
1105 if moved {
1106 self.mark_dirty();
1107 self.note_focus_moved_by_keyboard(true);
1108 if target.activate {
1109 self.on_activation_key(&KeyEvent::key_down(KeyCode::Space, " "));
1110 }
1111 }
1112 moved
1113 }
1114
1115 fn on_activation_key(&mut self, event: &KeyEvent) -> bool {
1116 if !plain_key_down(event)
1117 || !matches!(event.key_code, KeyCode::Enter | KeyCode::Space)
1118 || cranpose_ui::text_field_focus::has_focused_field()
1119 {
1120 return false;
1121 }
1122 let Some(focused) = cranpose_ui::active_focus_target() else {
1123 return false;
1124 };
1125 let activated = self.accessibility_activate(focused, None);
1126 self.note_focus_moved_by_keyboard(true);
1127 activated
1128 }
1129
1130 fn on_arrow_key(&mut self, event: &KeyEvent) -> bool {
1131 if !plain_key_down(event) || cranpose_ui::text_field_focus::has_focused_field() {
1132 return false;
1133 }
1134 if self.on_group_navigation_key(event) {
1135 return true;
1136 }
1137 let direction = match event.key_code {
1138 KeyCode::ArrowLeft => FocusDirection::Left,
1139 KeyCode::ArrowRight => FocusDirection::Right,
1140 KeyCode::ArrowUp => FocusDirection::Up,
1141 KeyCode::ArrowDown => FocusDirection::Down,
1142 _ => return false,
1143 };
1144 let Some(focused) = cranpose_ui::active_focus_target() else {
1145 return false;
1146 };
1147 let order = self.with_layout_tree(|layout_tree| {
1148 let layout_tree = layout_tree?;
1149 let group = cranpose_ui::selectable_group_of(layout_tree, focused)?;
1150 Some(cranpose_ui::collect_focus_order_under(layout_tree, group))
1151 });
1152 order.is_some_and(|order| self.move_focus_in_order(order, direction))
1153 }
1154
1155 fn note_focus_moved_by_keyboard(&mut self, keyboard: bool) {
1156 if cranpose_ui::set_keyboard_focus_visible(keyboard) {
1157 cranpose_ui::request_render_invalidation();
1158 self.mark_dirty();
1159 }
1160 }
1161
1162 fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
1163 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
1164 return false;
1165 }
1166 self.dismiss_top_modal_in_context()
1167 }
1168
1169 pub fn dismiss_top_modal(&mut self) -> bool {
1172 let _event_handler = enter_event_handler_scope();
1173 let app_context = Rc::clone(&self.shell.app.app_context);
1174 app_context.enter(|| self.dismiss_top_modal_in_context())
1175 }
1176
1177 fn dismiss_top_modal_in_context(&mut self) -> bool {
1178 let closed = run_in_mutable_snapshot(|| {
1179 if cranpose_ui::modal_depth() > 0 {
1180 cranpose_ui::dispatch_modal_back()
1181 } else {
1182 cranpose_ui::dismiss_top_popup()
1183 }
1184 })
1185 .unwrap_or(false);
1186 if closed {
1187 self.mark_dirty();
1188 }
1189 closed
1190 }
1191
1192 pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
1195 let order = self.with_layout_tree(|layout_tree| {
1196 layout_tree
1197 .map(cranpose_ui::collect_focus_order)
1198 .unwrap_or_default()
1199 });
1200 self.move_focus_in_order(order, direction)
1201 }
1202
1203 fn move_focus_in_order(
1204 &mut self,
1205 order: Vec<cranpose_ui::FocusEntry>,
1206 direction: FocusDirection,
1207 ) -> bool {
1208 cranpose_ui::set_focus_order(order);
1209 let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
1210 .unwrap_or(false);
1211 if moved {
1212 self.mark_dirty();
1213 self.note_focus_moved_by_keyboard(true);
1214 }
1215 moved
1216 }
1217
1218 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1221 let _event_handler = enter_event_handler_scope();
1222 let app_context = Rc::clone(&self.shell.app.app_context);
1223 app_context.enter(|| {
1224 let handled = self.on_key_event_inner(event);
1225 self.run_requested_focus_move();
1226 handled
1227 })
1228 }
1229
1230 fn run_requested_focus_move(&mut self) {
1232 if let Some(direction) = cranpose_ui::take_requested_focus_move() {
1233 self.move_focus_in_context(direction);
1234 }
1235 }
1236
1237 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1238 if self.inspector_key(event) {
1239 return true;
1240 }
1241 let root = self.key_event_root();
1242 if root.is_none() && self.id() != RootId::Primary {
1243 return false;
1244 }
1245 let route =
1246 cranpose_ui::KeyEventRoute::new(&mut self.shell.app.composition.applier_mut(), root);
1247 if self.dispatch_modifier_key(&route, event, true) {
1248 return true;
1249 }
1250 if route.focus_is_current() && self.on_focused_key_event(event) {
1251 return true;
1252 }
1253 if self.dispatch_modifier_key(&route, event, false) {
1254 return true;
1255 }
1256 if !route.focus_is_current() || cranpose_ui::text_field_focus::has_focused_field() {
1257 return false;
1258 }
1259 let handled = run_in_mutable_snapshot(|| cranpose_ui::dispatch_unhandled_key_event(event))
1260 .unwrap_or(false);
1261 if handled {
1262 self.mark_dirty();
1263 self.shell.app.request_layout_pass();
1264 }
1265 handled
1266 }
1267
1268 fn dispatch_modifier_key(
1269 &mut self,
1270 route: &cranpose_ui::KeyEventRoute,
1271 event: &KeyEvent,
1272 preview: bool,
1273 ) -> bool {
1274 let handled = run_in_mutable_snapshot(|| {
1275 let mut applier = self.shell.app.composition.applier_mut();
1276 if preview {
1277 route.dispatch_preview(&mut applier, event)
1278 } else {
1279 route.dispatch_bubble(&mut applier, event)
1280 }
1281 })
1282 .unwrap_or(false);
1283 if handled {
1284 self.mark_dirty();
1285 self.shell.app.request_layout_pass();
1286 }
1287 handled
1288 }
1289
1290 fn key_event_root(&self) -> Option<cranpose_core::NodeId> {
1291 self.surface()
1292 .modal_focus
1293 .last()
1294 .map(|entry| entry.0)
1295 .or_else(|| self.surface().root)
1296 .or_else(|| match self.id() {
1297 RootId::Primary => self.shell.app.composition.root(),
1298 RootId::Window(_) => None,
1299 })
1300 }
1301
1302 fn receives_focused_input(&mut self) -> bool {
1303 let root = self.key_event_root();
1304 cranpose_ui::KeyEventRoute::contains_focus(
1305 &mut self.shell.app.composition.applier_mut(),
1306 root,
1307 )
1308 }
1309
1310 fn on_focused_key_event(&mut self, event: &KeyEvent) -> bool {
1311 use KeyEventType::KeyDown;
1312
1313 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1314 #[cfg(all(
1315 feature = "clipboard-native",
1316 not(target_arch = "wasm32"),
1317 not(target_os = "android"),
1318 not(target_os = "ios")
1319 ))]
1320 {
1321 match event.key_code {
1322 KeyCode::C => {
1323 if let Some(text) = self.on_copy_inner() {
1324 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1325 return true;
1326 }
1327 }
1328 KeyCode::X => {
1329 if let Some(text) = self.on_cut_inner() {
1330 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1331 self.mark_dirty();
1332 self.shell.app.request_layout_pass();
1333 return true;
1334 }
1335 }
1336 KeyCode::V => {
1337 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1338 && self.on_paste_inner(&text)
1339 {
1340 return true;
1341 }
1342 }
1343 _ => {}
1344 }
1345 }
1346 }
1347
1348 if self.on_focus_key(event) {
1349 return true;
1350 }
1351
1352 if self.on_escape_key(event) {
1353 return true;
1354 }
1355
1356 if self.on_activation_key(event) || self.on_arrow_key(event) {
1357 return true;
1358 }
1359
1360 let handled =
1361 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1362 .unwrap_or(false);
1363
1364 if handled {
1365 self.mark_dirty();
1366 self.shell.app.request_layout_pass();
1367 }
1368
1369 handled
1370 }
1371
1372 pub fn on_paste(&mut self, text: &str) -> bool {
1375 if self.inspector_owns_keyboard() {
1376 return true;
1377 }
1378 let _event_handler = enter_event_handler_scope();
1379 let app_context = Rc::clone(&self.shell.app.app_context);
1380 app_context.enter(|| self.on_paste_inner(text))
1381 }
1382
1383 fn on_paste_inner(&mut self, text: &str) -> bool {
1384 if !self.receives_focused_input() {
1385 return false;
1386 }
1387 let handled =
1388 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1389 .unwrap_or(false);
1390
1391 if handled {
1392 self.mark_dirty();
1393 self.shell.app.request_layout_pass();
1394 }
1395
1396 handled
1397 }
1398
1399 pub fn on_copy(&mut self) -> Option<String> {
1402 let app_context = Rc::clone(&self.shell.app.app_context);
1403 if self.inspector_owns_keyboard() {
1404 return None;
1405 }
1406 app_context.enter(|| self.on_copy_inner())
1407 }
1408
1409 fn on_copy_inner(&mut self) -> Option<String> {
1410 if !self.receives_focused_input() {
1411 return None;
1412 }
1413 cranpose_ui::text_field_focus::dispatch_copy()
1414 }
1415
1416 pub fn on_cut(&mut self) -> Option<String> {
1419 let _event_handler = enter_event_handler_scope();
1420 if self.inspector_owns_keyboard() {
1421 return None;
1422 }
1423 let app_context = Rc::clone(&self.shell.app.app_context);
1424 app_context.enter(|| self.on_cut_inner())
1425 }
1426
1427 fn on_cut_inner(&mut self) -> Option<String> {
1428 if !self.receives_focused_input() {
1429 return None;
1430 }
1431 let text =
1432 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1433
1434 if text.is_some() {
1435 self.mark_dirty();
1436 self.shell.app.request_layout_pass();
1437 }
1438
1439 text
1440 }
1441
1442 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1450 if self.inspector_owns_keyboard() {
1451 return true;
1452 }
1453 let _event_handler = enter_event_handler_scope();
1454 let app_context = Rc::clone(&self.shell.app.app_context);
1455 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1456 }
1457
1458 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1459 if !self.receives_focused_input() {
1460 return false;
1461 }
1462 let handled = run_in_mutable_snapshot(|| {
1463 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1464 })
1465 .unwrap_or(false);
1466
1467 if handled {
1468 self.mark_dirty();
1469 self.shell.app.request_layout_pass();
1470 }
1471
1472 handled
1473 }
1474
1475 pub fn on_ime_finish_composing(&mut self) -> bool {
1479 if self.inspector_owns_keyboard() {
1480 return true;
1481 }
1482 let _event_handler = enter_event_handler_scope();
1483 let app_context = Rc::clone(&self.shell.app.app_context);
1484 app_context.enter(|| self.on_ime_finish_composing_inner())
1485 }
1486
1487 fn on_ime_finish_composing_inner(&mut self) -> bool {
1488 if !self.receives_focused_input() {
1489 return false;
1490 }
1491 let handled =
1492 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1493 .unwrap_or(false);
1494
1495 if handled {
1496 self.mark_dirty();
1497 self.shell.app.request_layout_pass();
1498 }
1499
1500 handled
1501 }
1502
1503 pub fn on_ime_action(&mut self, action: cranpose_ui::ImeAction) -> bool {
1509 if self.inspector_owns_keyboard() {
1510 return true;
1511 }
1512 let _event_handler = enter_event_handler_scope();
1513 let app_context = Rc::clone(&self.shell.app.app_context);
1514 app_context.enter(|| {
1515 if !self.receives_focused_input() {
1516 return false;
1517 }
1518 let handled = run_in_mutable_snapshot(|| {
1519 cranpose_ui::text_field_focus::dispatch_ime_action(action)
1520 })
1521 .unwrap_or(false);
1522 if handled {
1523 self.mark_dirty();
1524 self.shell.app.request_layout_pass();
1525 }
1526 self.run_requested_focus_move();
1527 handled
1528 })
1529 }
1530
1531 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1535 if self.inspector_owns_keyboard() {
1536 return true;
1537 }
1538 let _event_handler = enter_event_handler_scope();
1539 let app_context = Rc::clone(&self.shell.app.app_context);
1540 app_context.enter(|| {
1541 if !self.receives_focused_input() {
1542 return false;
1543 }
1544 let handled = run_in_mutable_snapshot(|| {
1545 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1546 start_bytes,
1547 end_bytes,
1548 )
1549 })
1550 .unwrap_or(false);
1551
1552 if handled {
1553 self.mark_dirty();
1554 self.shell.app.request_layout_pass();
1555 }
1556
1557 handled
1558 })
1559 }
1560
1561 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1566 if self.inspector_owns_keyboard() {
1567 return true;
1568 }
1569 let _event_handler = enter_event_handler_scope();
1570 let app_context = Rc::clone(&self.shell.app.app_context);
1571 app_context.enter(|| {
1572 if !self.receives_focused_input() {
1573 return false;
1574 }
1575 let handled = run_in_mutable_snapshot(|| {
1576 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1577 })
1578 .unwrap_or(false);
1579
1580 if handled {
1581 self.mark_dirty();
1582 }
1583
1584 handled
1585 })
1586 }
1587
1588 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1592 let app_context = Rc::clone(&self.shell.app.app_context);
1593 app_context.enter(|| {
1594 self.receives_focused_input()
1595 .then(cranpose_ui::text_field_focus::focused_editor_state)
1596 .flatten()
1597 })
1598 }
1599
1600 pub fn ime_caret_geometry(
1604 &mut self,
1605 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1606 let app_context = Rc::clone(&self.shell.app.app_context);
1607 app_context.enter(|| {
1608 self.receives_focused_input()
1609 .then(cranpose_ui::text_field_focus::focused_caret_geometry)
1610 .flatten()
1611 })
1612 }
1613
1614 pub fn ime_field_bounds(&mut self) -> Option<cranpose_ui_graphics::Rect> {
1618 let app_context = Rc::clone(&self.shell.app.app_context);
1619 app_context.enter(|| {
1620 self.receives_focused_input()
1621 .then(cranpose_ui::text_field_focus::focused_field_bounds)
1622 .flatten()
1623 })
1624 }
1625
1626 pub fn ime_field_is_password(&mut self) -> bool {
1633 let app_context = Rc::clone(&self.shell.app.app_context);
1634 app_context.enter(|| {
1635 let Some(field) = self
1636 .receives_focused_input()
1637 .then(cranpose_ui::text_field_focus::focused_field_target)
1638 .flatten()
1639 else {
1640 return false;
1641 };
1642 let mut applier = self.shell.app.composition.applier_mut();
1643 applier
1644 .with_node::<LayoutNode, _>(field, |node| node.semantics_configuration())
1645 .or_else(|_| {
1646 applier.with_node::<SubcomposeLayoutNode, _>(field, |node| {
1647 node.semantics_configuration()
1648 })
1649 })
1650 .ok()
1651 .flatten()
1652 .is_some_and(|config| config.password)
1653 })
1654 }
1655
1656 pub fn clear_text_field_focus(&mut self) {
1659 let _event_handler = enter_event_handler_scope();
1660 let app_context = Rc::clone(&self.shell.app.app_context);
1661 app_context.enter(|| {
1662 if self.receives_focused_input() {
1663 cranpose_ui::text_field_focus::clear_focus();
1664 self.mark_dirty();
1665 self.shell.app.request_layout_pass();
1666 }
1667 });
1668 }
1669
1670 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1673 if self.inspector_owns_keyboard() {
1674 return true;
1675 }
1676 let _event_handler = enter_event_handler_scope();
1677 let app_context = Rc::clone(&self.shell.app.app_context);
1678 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1679 }
1680
1681 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1682 if !self.receives_focused_input() {
1683 return false;
1684 }
1685 let handled = run_in_mutable_snapshot(|| {
1686 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1687 })
1688 .unwrap_or(false);
1689
1690 if handled {
1691 self.mark_dirty();
1692 self.shell.app.request_layout_pass();
1693 }
1694
1695 handled
1696 }
1697}
1698
1699impl<R> AppShell<R>
1700where
1701 R: Renderer,
1702 R::Error: Debug,
1703{
1704 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
1713 self.app.modifiers = Some(modifiers);
1714 }
1715
1716 pub fn modifiers(&self) -> Option<Modifiers> {
1720 self.app.modifiers
1721 }
1722
1723 pub fn rotary_scroll_factor(&self) -> f32 {
1726 self.app.rotary_scroll_factor
1727 }
1728
1729 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
1738 if factor.is_finite() && factor > 0.0 {
1739 self.app.rotary_scroll_factor = factor;
1740 }
1741 }
1742
1743 pub fn notify_app_paused(&mut self) {
1750 let app_context = Rc::clone(&self.app.app_context);
1751 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
1752 }
1753
1754 pub fn notify_app_resumed(&mut self) -> bool {
1762 let app_context = Rc::clone(&self.app.app_context);
1763 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
1764 }
1765
1766 #[cfg(all(
1768 feature = "clipboard-native",
1769 target_os = "linux",
1770 not(target_arch = "wasm32")
1771 ))]
1772 pub fn set_primary_selection(&mut self, text: &str) {
1773 use arboard::{LinuxClipboardKind, SetExtLinux};
1774 if let Some(ref mut clipboard) = self.app.clipboard {
1775 let result = clipboard
1776 .set()
1777 .clipboard(LinuxClipboardKind::Primary)
1778 .text(text.to_string());
1779 if let Err(e) = result {
1780 log::debug!("Primary selection set failed: {e:?}");
1781 }
1782 }
1783 }
1784
1785 #[cfg(not(all(
1788 feature = "clipboard-native",
1789 target_os = "linux",
1790 not(target_arch = "wasm32")
1791 )))]
1792 pub fn set_primary_selection(&mut self, _text: &str) {}
1793
1794 #[cfg(all(
1796 feature = "clipboard-native",
1797 target_os = "linux",
1798 not(target_arch = "wasm32")
1799 ))]
1800 pub fn get_primary_selection(&mut self) -> Option<String> {
1801 use arboard::{GetExtLinux, LinuxClipboardKind};
1802 if let Some(ref mut clipboard) = self.app.clipboard {
1803 clipboard
1804 .get()
1805 .clipboard(LinuxClipboardKind::Primary)
1806 .text()
1807 .ok()
1808 } else {
1809 None
1810 }
1811 }
1812
1813 #[cfg(not(all(
1815 feature = "clipboard-native",
1816 target_os = "linux",
1817 not(target_arch = "wasm32")
1818 )))]
1819 pub fn get_primary_selection(&mut self) -> Option<String> {
1820 None
1821 }
1822
1823 pub fn sync_selection_to_primary(&mut self) {
1826 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1827 {
1828 if let Some(text) = self.on_copy() {
1829 self.set_primary_selection(&text);
1830 }
1831 }
1832 }
1833
1834 pub fn set_pointer_source(&mut self, source: PointerSource) {
1836 self.primary().set_pointer_source(source);
1837 }
1838
1839 pub fn pointer_source(&self) -> PointerSource {
1841 self.surfaces[0].pointer_source
1842 }
1843
1844 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
1846 self.primary().set_cursor(x, y)
1847 }
1848
1849 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
1851 self.primary().set_cursor_at_time(x, y, time_ms)
1852 }
1853
1854 pub fn set_cursor_at_event_time(
1856 &mut self,
1857 x: f32,
1858 y: f32,
1859 event_time: PointerEventTime,
1860 ) -> bool {
1861 self.primary().set_cursor_at_event_time(x, y, event_time)
1862 }
1863
1864 pub fn pointer_pressed(&mut self) -> bool {
1866 self.primary().pointer_pressed()
1867 }
1868
1869 pub fn accessibility_activate(&mut self, node_id: NodeId, canvas_key: Option<u64>) -> bool {
1871 self.primary().accessibility_activate(node_id, canvas_key)
1872 }
1873
1874 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
1876 self.primary().pointer_pressed_at_time(time_ms)
1877 }
1878
1879 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
1881 self.primary().pointer_pressed_at_event_time(event_time)
1882 }
1883
1884 pub fn pointer_released(&mut self) -> bool {
1886 self.primary().pointer_released()
1887 }
1888
1889 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
1891 self.primary().pointer_released_at_position(x, y)
1892 }
1893
1894 pub fn pointer_released_at_position_time(
1897 &mut self,
1898 x: f32,
1899 y: f32,
1900 time_ms: Option<i64>,
1901 ) -> bool {
1902 self.primary()
1903 .pointer_released_at_position_time(x, y, time_ms)
1904 }
1905
1906 pub fn pointer_released_at_position_event_time(
1909 &mut self,
1910 x: f32,
1911 y: f32,
1912 event_time: PointerEventTime,
1913 ) -> bool {
1914 self.primary()
1915 .pointer_released_at_position_event_time(x, y, event_time)
1916 }
1917
1918 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
1920 self.primary().pointer_released_at_time(time_ms)
1921 }
1922
1923 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
1925 self.primary().pointer_released_at_event_time(event_time)
1926 }
1927
1928 pub fn secondary_pointer_pressed(
1930 &mut self,
1931 pointer_id: u64,
1932 x: f32,
1933 y: f32,
1934 time_ms: Option<i64>,
1935 ) -> bool {
1936 self.primary()
1937 .secondary_pointer_pressed(pointer_id, x, y, time_ms)
1938 }
1939
1940 pub fn secondary_pointer_moved(
1942 &mut self,
1943 pointer_id: u64,
1944 x: f32,
1945 y: f32,
1946 time_ms: Option<i64>,
1947 ) -> bool {
1948 self.primary()
1949 .secondary_pointer_moved(pointer_id, x, y, time_ms)
1950 }
1951
1952 pub fn secondary_pointer_released(
1954 &mut self,
1955 pointer_id: u64,
1956 x: f32,
1957 y: f32,
1958 time_ms: Option<i64>,
1959 ) -> bool {
1960 self.primary()
1961 .secondary_pointer_released(pointer_id, x, y, time_ms)
1962 }
1963
1964 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
1966 self.primary().pointer_zoomed(zoom_factor)
1967 }
1968
1969 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
1971 self.primary().wheel_scrolled(wheel)
1972 }
1973
1974 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
1976 self.primary().pointer_scrolled(delta_x, delta_y)
1977 }
1978
1979 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
1981 where
1982 F: Fn(RotaryScrollEvent) -> bool + 'static,
1983 {
1984 self.primary().set_on_rotary_scroll(handler);
1985 }
1986
1987 pub fn clear_on_rotary_scroll(&mut self) {
1989 self.primary().clear_on_rotary_scroll();
1990 }
1991
1992 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
1994 self.primary()
1995 .rotary_scrolled_by_detents(detents, uptime_millis)
1996 }
1997
1998 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
2000 self.primary().rotary_scrolled(event)
2001 }
2002
2003 pub fn cancel_gesture(&mut self) {
2005 self.primary().cancel_gesture();
2006 }
2007
2008 pub fn cancel_gesture_unless_pressed(&mut self) {
2010 self.primary().cancel_gesture_unless_pressed();
2011 }
2012
2013 pub fn refresh_pointer_icon(&self) {
2017 self.surfaces[0].pointer_icon.refresh();
2018 }
2019
2020 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
2027 self.surfaces[0].pointer_icon.take_change()
2028 }
2029
2030 pub fn set_platform_text_input(
2037 &mut self,
2038 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
2039 ) {
2040 self.primary().set_platform_text_input(handler);
2041 }
2042
2043 pub fn dismiss_top_modal(&mut self) -> bool {
2045 self.primary().dismiss_top_modal()
2046 }
2047
2048 pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
2050 self.primary().move_focus_in_context(direction)
2051 }
2052
2053 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
2055 self.primary().on_key_event(event)
2056 }
2057
2058 pub fn on_paste(&mut self, text: &str) -> bool {
2060 self.primary().on_paste(text)
2061 }
2062
2063 pub fn on_copy(&mut self) -> Option<String> {
2065 self.primary().on_copy()
2066 }
2067
2068 pub fn on_cut(&mut self) -> Option<String> {
2070 self.primary().on_cut()
2071 }
2072
2073 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
2075 self.primary().on_ime_preedit(text, cursor)
2076 }
2077
2078 pub fn on_ime_finish_composing(&mut self) -> bool {
2080 self.primary().on_ime_finish_composing()
2081 }
2082
2083 pub fn on_ime_action(&mut self, action: cranpose_ui::ImeAction) -> bool {
2085 self.primary().on_ime_action(action)
2086 }
2087
2088 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
2090 self.primary()
2091 .on_ime_set_composing_region(start_bytes, end_bytes)
2092 }
2093
2094 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
2096 self.primary().on_ime_set_selection(start_bytes, end_bytes)
2097 }
2098
2099 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
2101 self.primary().ime_editor_state()
2102 }
2103
2104 pub fn ime_caret_geometry(
2106 &mut self,
2107 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
2108 self.primary().ime_caret_geometry()
2109 }
2110
2111 pub fn ime_field_bounds(&mut self) -> Option<cranpose_ui_graphics::Rect> {
2113 self.primary().ime_field_bounds()
2114 }
2115
2116 pub fn ime_field_is_password(&mut self) -> bool {
2118 self.primary().ime_field_is_password()
2119 }
2120
2121 pub fn clear_text_field_focus(&mut self) {
2123 self.primary().clear_text_field_focus();
2124 }
2125
2126 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
2128 self.primary()
2129 .on_ime_delete_surrounding(before_bytes, after_bytes)
2130 }
2131}
2132
2133enum ActivationTarget {
2134 Direct(cranpose_foundation::SemanticsCustomAction),
2135 Edit(NodeId),
2136 Pointer(NodeId, Option<Rect>),
2137}
2138
2139fn activation_target(
2140 node: &cranpose_ui::SemanticsNode,
2141 node_id: NodeId,
2142 canvas_key: Option<u64>,
2143) -> Option<ActivationTarget> {
2144 if node.hidden {
2145 return None;
2146 }
2147 if node.node_id != node_id {
2148 return node
2149 .children
2150 .iter()
2151 .find_map(|child| activation_target(child, node_id, canvas_key));
2152 }
2153 if !node.enabled {
2154 return None;
2155 }
2156 if let Some(key) = canvas_key {
2157 let child = node
2158 .details()
2159 .canvas_children
2160 .iter()
2161 .find(|child| child.key == key)?;
2162 return (child.enabled && child.clickable)
2163 .then_some(ActivationTarget::Pointer(node_id, Some(child.bounds)));
2164 }
2165 if let Some(action) = &node.on_click {
2166 return Some(ActivationTarget::Direct(action.clone()));
2167 }
2168 if node.details().editable_text && node.focusable {
2169 return Some(ActivationTarget::Edit(node_id));
2170 }
2171 node.actions.first().map(|action| match action {
2172 cranpose_ui::SemanticsAction::Click { handler } => {
2173 ActivationTarget::Pointer(handler.node_id(), None)
2174 }
2175 })
2176}
2177
2178fn activation_input(
2179 root: &LayoutBox,
2180 node_id: NodeId,
2181 canvas_bounds: Option<Rect>,
2182) -> Option<(Point, Point, Rc<cranpose_ui::ModifierNodeSlices>)> {
2183 if root.node_id != node_id {
2184 return root
2185 .children
2186 .iter()
2187 .find_map(|child| activation_input(child, node_id, canvas_bounds));
2188 }
2189 let bounds = canvas_bounds.map_or(root.rect, |bounds| Rect {
2190 x: root.rect.x + bounds.x,
2191 y: root.rect.y + bounds.y,
2192 ..bounds
2193 });
2194 let position = Point {
2195 x: bounds.x + bounds.width * 0.5,
2196 y: bounds.y + bounds.height * 0.5,
2197 };
2198 let local = Point {
2199 x: position.x - root.rect.x,
2200 y: position.y - root.rect.y,
2201 };
2202 (bounds.width > 0.0 && bounds.height > 0.0 && position.x.is_finite() && position.y.is_finite())
2203 .then(|| (position, local, Rc::clone(&root.node_data.modifier_slices)))
2204}
2205
2206fn plain_key_down(event: &KeyEvent) -> bool {
2207 event.event_type == KeyEventType::KeyDown
2208 && !event.modifiers.ctrl
2209 && !event.modifiers.meta
2210 && !event.modifiers.alt
2211}