1use cranpose_ui::FocusDirection;
2
3use super::*;
4
5impl<R> AppShell<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 mut event = PointerEvent::new(kind, position, global_position)
18 .with_time_ms(event_time.platform_time_ms)
19 .with_animation_time_nanos(event_time.animation_time_nanos);
20 event.modifiers = self.modifiers;
21 event
22 }
23
24 fn resolve_gesture_targets(
25 &self,
26 pointer: PointerId,
27 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
28 self.resolve_hit_path(pointer)
29 }
30
31 fn resolve_hit_path(
32 &self,
33 pointer: PointerId,
34 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
35 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
36 return Vec::new();
37 };
38
39 let scene = self.renderer.scene();
40 let targets: Vec<_> = node_ids
41 .iter()
42 .filter_map(|&id| scene.find_target(id))
43 .collect();
44 log::trace!(
45 target: "cranpose::input",
46 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
47 targets.len()
48 );
49 targets
50 }
51
52 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
53 where
54 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
55 {
56 let mut applier = self.composition.applier_mut();
57 for target in targets {
58 let node_id = target.node_id();
59 target.dispatch_with_applier(&mut applier, event.clone());
60 log::trace!(
61 target: "cranpose::input",
62 "dispatch {:?} node={} consumed={} stop_on_consume={}",
63 event.kind,
64 node_id,
65 event.is_consumed(),
66 stop_on_consume,
67 );
68 if stop_on_consume && event.is_consumed() {
69 break;
70 }
71 }
72 event.finish_post_dispatch();
73 }
74
75 fn dispatch_to_hits(
76 &mut self,
77 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
78 event: PointerEvent,
79 ) -> bool {
80 let capture_paths = hits
81 .iter()
82 .map(|hit| hit.capture_path())
83 .collect::<Vec<_>>();
84 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
85 .into_iter()
86 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
87 .collect::<Vec<_>>();
88
89 self.dispatch_targets(targets, event.clone(), true);
90
91 event.is_consumed()
92 }
93
94 pub fn set_pointer_source(&mut self, source: PointerSource) {
100 self.pointer_source = source;
101 }
102
103 pub fn pointer_source(&self) -> PointerSource {
105 self.pointer_source
106 }
107
108 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
117 self.modifiers = Some(modifiers);
118 }
119
120 pub fn modifiers(&self) -> Option<Modifiers> {
124 self.modifiers
125 }
126
127 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
128 self.set_cursor_at_time(x, y, None)
129 }
130
131 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
138 let event_time = self.realtime_pointer_event_time(time_ms);
139 self.set_cursor_at_event_time(x, y, event_time)
140 }
141
142 pub fn set_cursor_at_event_time(
144 &mut self,
145 x: f32,
146 y: f32,
147 event_time: PointerEventTime,
148 ) -> bool {
149 let _event_handler = enter_event_handler_scope();
150 let app_context = Rc::clone(&self.app_context);
151 let result = app_context.enter(|| {
152 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
153 });
154 if result {
155 self.mark_dirty();
156 }
157 log::trace!(
158 target: "cranpose::input",
159 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
160 event_time.platform_time_ms,
161 event_time.animation_time_nanos,
162 );
163 result
164 }
165
166 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
167 self.cursor = (x, y);
168
169 if self.buttons_pressed != PointerButtons::NONE {
170 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
171 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
172 if !targets.is_empty() {
173 let event = self
174 .pointer_event(
175 PointerEventKind::Move,
176 Point { x, y },
177 Point { x, y },
178 event_time,
179 )
180 .with_buttons(self.buttons_pressed)
181 .with_source(self.pointer_source);
182 self.dispatch_targets(targets, event, false);
183 return true;
184 }
185
186 return false;
187 }
188
189 return false;
190 }
191
192 let hits = self.renderer.scene().hit_test(x, y);
193 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
194
195 let pos = Point { x, y };
196 let previously_hovered = self.hovered_nodes.clone();
197 for old_id in previously_hovered {
198 if !new_ids.contains(&old_id)
199 && let Some(target) = self.renderer.scene().find_target(old_id)
200 {
201 let exit_event = self
202 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
203 .with_buttons(self.buttons_pressed)
204 .with_source(self.pointer_source);
205 self.dispatch_targets(std::iter::once(target), exit_event, false);
206 }
207 }
208
209 for hit in &hits {
210 if !self.hovered_nodes.contains(&hit.node_id()) {
211 let enter_event = self
212 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
213 .with_buttons(self.buttons_pressed)
214 .with_source(self.pointer_source);
215 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
216 }
217 }
218
219 self.hovered_nodes = new_ids;
220 self.apply_hovered_pointer_icon(&hits);
221
222 if !hits.is_empty() {
223 let event = self
224 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
225 .with_buttons(self.buttons_pressed)
226 .with_source(self.pointer_source);
227 self.dispatch_targets(hits, event, true);
228 true
229 } else {
230 false
231 }
232 }
233
234 pub fn pointer_pressed(&mut self) -> bool {
235 self.pointer_pressed_at_time(None)
236 }
237
238 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
241 let event_time = self.realtime_pointer_event_time(time_ms);
242 self.pointer_pressed_at_event_time(event_time)
243 }
244
245 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
247 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
248 return true;
249 }
250 let _event_handler = enter_event_handler_scope();
251 let app_context = Rc::clone(&self.app_context);
252 let result = app_context.enter(|| {
253 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
254 });
255 if result {
256 self.mark_dirty();
257 }
258 log::trace!(
259 target: "cranpose::input",
260 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
261 event_time.platform_time_ms,
262 event_time.animation_time_nanos,
263 );
264 result
265 }
266
267 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
268 self.note_focus_moved_by_keyboard(false);
269 self.buttons_pressed.insert(PointerButton::Primary);
270
271 let mut hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
272 if self.pointer_source.is_touch_like()
273 && let Some(near) = self
274 .renderer
275 .scene()
276 .hit_test_near(self.cursor.0, self.cursor.1)
277 && hits.iter().all(|hit| {
278 hit.node_id() != near.node_id() && near.capture_path().contains(&hit.node_id())
279 })
280 {
281 hits.insert(0, near);
282 }
283 if hits.is_empty() {
284 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
285 false
286 } else {
287 let event = self
288 .pointer_event(
289 PointerEventKind::Down,
290 Point {
291 x: self.cursor.0,
292 y: self.cursor.1,
293 },
294 Point {
295 x: self.cursor.0,
296 y: self.cursor.1,
297 },
298 event_time,
299 )
300 .with_buttons(self.buttons_pressed)
301 .with_source(self.pointer_source);
302
303 let mut delivered_capture_paths = Vec::new();
304 let mut applier = self.composition.applier_mut();
305 for hit in hits {
306 let node_id = hit.node_id();
307 delivered_capture_paths.push(hit.capture_path());
308 hit.dispatch_with_applier(&mut applier, event.clone());
309 log::trace!(
310 target: "cranpose::input",
311 "dispatch {:?} node={} consumed={} stop_on_consume=true",
312 event.kind,
313 node_id,
314 event.is_consumed(),
315 );
316 if event.is_consumed() {
317 break;
318 }
319 }
320
321 self.hit_path_tracker
322 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
323 log::trace!(
324 target: "cranpose::input",
325 "pointer_pressed_inner cached_hit_path={:?}",
326 self.hit_path_tracker.get_path(PointerId::PRIMARY),
327 );
328
329 true
330 }
331 }
332
333 pub fn pointer_released(&mut self) -> bool {
334 self.pointer_released_at_time(None)
335 }
336
337 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
350 self.pointer_released_at_position_time(x, y, None)
351 }
352
353 pub fn pointer_released_at_position_time(
357 &mut self,
358 x: f32,
359 y: f32,
360 time_ms: Option<i64>,
361 ) -> bool {
362 let event_time = self.realtime_pointer_event_time(time_ms);
363 self.pointer_released_at_position_event_time(x, y, event_time)
364 }
365
366 pub fn pointer_released_at_position_event_time(
368 &mut self,
369 x: f32,
370 y: f32,
371 event_time: PointerEventTime,
372 ) -> bool {
373 let _event_handler = enter_event_handler_scope();
374 let app_context = Rc::clone(&self.app_context);
375 let result = app_context.enter(|| {
376 run_in_mutable_snapshot(|| {
377 self.cursor = (x, y);
378 self.pointer_released_inner(event_time)
379 })
380 .unwrap_or(false)
381 });
382 if result {
383 self.mark_dirty();
384 }
385 log::trace!(
386 target: "cranpose::input",
387 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
388 event_time.platform_time_ms,
389 event_time.animation_time_nanos,
390 );
391 result
392 }
393
394 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
397 let event_time = self.realtime_pointer_event_time(time_ms);
398 self.pointer_released_at_event_time(event_time)
399 }
400
401 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
403 let _event_handler = enter_event_handler_scope();
404 let app_context = Rc::clone(&self.app_context);
405 let result = app_context.enter(|| {
406 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
407 });
408 if result {
409 self.mark_dirty();
410 }
411 log::trace!(
412 target: "cranpose::input",
413 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
414 event_time.platform_time_ms,
415 event_time.animation_time_nanos,
416 );
417 result
418 }
419
420 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
421 self.buttons_pressed.remove(PointerButton::Primary);
422 let corrected_buttons = self.buttons_pressed;
423 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
424
425 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
426
427 if !targets.is_empty() {
428 let event = self
429 .pointer_event(
430 PointerEventKind::Up,
431 Point {
432 x: self.cursor.0,
433 y: self.cursor.1,
434 },
435 Point {
436 x: self.cursor.0,
437 y: self.cursor.1,
438 },
439 event_time,
440 )
441 .with_buttons(corrected_buttons)
442 .with_source(self.pointer_source);
443
444 self.dispatch_targets(targets, event, false);
445 true
446 } else {
447 false
448 }
449 }
450
451 pub fn secondary_pointer_pressed(
460 &mut self,
461 pointer_id: u64,
462 x: f32,
463 y: f32,
464 time_ms: Option<i64>,
465 ) -> bool {
466 let event_time = self.realtime_pointer_event_time(time_ms);
467 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
468 }
469
470 pub fn secondary_pointer_moved(
472 &mut self,
473 pointer_id: u64,
474 x: f32,
475 y: f32,
476 time_ms: Option<i64>,
477 ) -> bool {
478 let event_time = self.realtime_pointer_event_time(time_ms);
479 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
480 }
481
482 pub fn secondary_pointer_released(
484 &mut self,
485 pointer_id: u64,
486 x: f32,
487 y: f32,
488 time_ms: Option<i64>,
489 ) -> bool {
490 let event_time = self.realtime_pointer_event_time(time_ms);
491 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
492 }
493
494 fn dispatch_secondary_pointer(
495 &mut self,
496 kind: PointerEventKind,
497 pointer_id: u64,
498 x: f32,
499 y: f32,
500 event_time: PointerEventTime,
501 ) -> bool {
502 if pointer_id == 0 {
503 log::warn!(
504 target: "cranpose::input",
505 "secondary pointer dispatch called with the primary pointer id"
506 );
507 return false;
508 }
509
510 let _event_handler = enter_event_handler_scope();
511 let app_context = Rc::clone(&self.app_context);
512 let result = app_context.enter(|| {
513 run_in_mutable_snapshot(|| {
514 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
515 return false;
516 }
517 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
518 if targets.is_empty() {
519 return false;
520 }
521 let pos = Point { x, y };
522 let event = self
523 .pointer_event(kind, pos, pos, event_time)
524 .with_buttons(self.buttons_pressed)
525 .with_id(pointer_id)
526 .with_source(self.pointer_source);
527 self.dispatch_targets(targets, event, false);
528 true
529 })
530 .unwrap_or(false)
531 });
532 if result {
533 self.mark_dirty();
534 }
535 log::trace!(
536 target: "cranpose::input",
537 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
538 event_time.platform_time_ms,
539 event_time.animation_time_nanos,
540 );
541 result
542 }
543
544 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
550 let event_time = self.realtime_pointer_event_time(None);
551 let _event_handler = enter_event_handler_scope();
552 let app_context = Rc::clone(&self.app_context);
553 let result = app_context.enter(|| {
554 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
555 .unwrap_or(false)
556 });
557 if result {
558 self.mark_dirty();
559 }
560 log::trace!(
561 target: "cranpose::input",
562 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
563 );
564 result
565 }
566
567 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
568 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
569 return false;
570 }
571
572 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
573 if hits.is_empty() {
574 return false;
575 }
576
577 let pos = Point {
578 x: self.cursor.0,
579 y: self.cursor.1,
580 };
581 let event = self
582 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
583 .with_buttons(self.buttons_pressed)
584 .with_zoom_delta(zoom_factor)
585 .with_source(self.pointer_source);
586
587 self.dispatch_to_hits(&hits, event)
588 }
589
590 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
615 if wheel.is_zoom() {
616 let zoom_factor = wheel.zoom_factor();
617 log::trace!(
618 target: "cranpose::input",
619 "wheel zoom factor={zoom_factor:.4}"
620 );
621 return self.pointer_zoomed(zoom_factor);
622 }
623
624 let rotary =
625 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
626 if self.rotary_scrolled(rotary) {
627 return true;
628 }
629
630 let delta = wheel.scroll_delta();
631 log::trace!(
632 target: "cranpose::input",
633 "wheel delta ({:.2},{:.2}) alt={}",
634 delta.x,
635 delta.y,
636 wheel.modifiers.alt
637 );
638 self.pointer_scrolled(delta.x, delta.y)
639 }
640
641 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
649 let event_time = self.realtime_pointer_event_time(None);
650 let _event_handler = enter_event_handler_scope();
651 let app_context = Rc::clone(&self.app_context);
652 let result = app_context.enter(|| {
653 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
654 .unwrap_or(false)
655 });
656 if result {
657 self.mark_dirty();
658 }
659 log::trace!(
660 target: "cranpose::input",
661 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
662 );
663 result
664 }
665
666 fn pointer_scrolled_inner(
667 &mut self,
668 delta_x: f32,
669 delta_y: f32,
670 event_time: PointerEventTime,
671 ) -> bool {
672 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
673 return false;
674 }
675
676 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
677 if hits.is_empty() {
678 return false;
679 }
680
681 let event = self
682 .pointer_event(
683 PointerEventKind::Scroll,
684 Point {
685 x: self.cursor.0,
686 y: self.cursor.1,
687 },
688 Point {
689 x: self.cursor.0,
690 y: self.cursor.1,
691 },
692 event_time,
693 )
694 .with_buttons(self.buttons_pressed)
695 .with_scroll_delta(Point {
696 x: delta_x,
697 y: delta_y,
698 })
699 .with_source(self.pointer_source);
700
701 self.dispatch_to_hits(&hits, event)
702 }
703
704 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
715 where
716 F: Fn(RotaryScrollEvent) -> bool + 'static,
717 {
718 self.on_rotary_scroll = Some(Rc::new(handler));
719 }
720
721 pub fn clear_on_rotary_scroll(&mut self) {
723 self.on_rotary_scroll = None;
724 }
725
726 pub fn rotary_scroll_factor(&self) -> f32 {
729 self.rotary_scroll_factor
730 }
731
732 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
741 if factor.is_finite() && factor > 0.0 {
742 self.rotary_scroll_factor = factor;
743 }
744 }
745
746 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
752 let factor = self.rotary_scroll_factor;
753 self.rotary_scrolled(RotaryScrollEvent::from_detents(
754 detents,
755 factor,
756 factor,
757 uptime_millis,
758 ))
759 }
760
761 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
782 let _event_handler = enter_event_handler_scope();
783 let app_context = Rc::clone(&self.app_context);
784 let result = app_context.enter(|| {
785 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
786 });
787 if result {
788 self.mark_dirty();
789 }
790 log::trace!(
791 target: "cranpose::input",
792 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
793 event.vertical_scroll_pixels,
794 event.horizontal_scroll_pixels,
795 event.uptime_millis,
796 );
797 result
798 }
799
800 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
801 if rotary.is_empty() {
802 return false;
803 }
804
805 let bubble_order = self.rotary_dispatch_order();
806 let position = Point {
807 x: self.cursor.0,
808 y: self.cursor.1,
809 };
810
811 if !bubble_order.is_empty() {
812 let capture_targets = bubble_order
813 .iter()
814 .rev()
815 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
816 .collect::<Vec<_>>();
817 let mut capture_event =
818 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
819 capture_event.modifiers = self.modifiers;
820 self.dispatch_targets(capture_targets, capture_event.clone(), true);
821 if capture_event.is_consumed() {
822 return true;
823 }
824
825 let bubble_targets = bubble_order
826 .iter()
827 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
828 .collect::<Vec<_>>();
829 let mut bubble_event =
830 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
831 bubble_event.modifiers = self.modifiers;
832 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
833 if bubble_event.is_consumed() {
834 return true;
835 }
836 }
837
838 if let Some(handler) = self.on_rotary_scroll.clone() {
839 return handler(rotary);
840 }
841
842 false
843 }
844
845 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
846 if let Some(focused) = cranpose_ui::active_focus_target()
847 && let Some(target) = self.renderer.scene().find_target(focused)
848 {
849 let path = target.capture_path();
850 if !path.is_empty() {
851 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
852 }
853 }
854
855 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
856 if hits.is_empty() {
857 return Vec::new();
858 }
859 let capture_paths = hits
860 .iter()
861 .map(|hit| hit.capture_path())
862 .collect::<Vec<_>>();
863 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
864 }
865
866 pub fn cancel_gesture(&mut self) {
872 let event_time = self.realtime_pointer_event_time(None);
873 let _event_handler = enter_event_handler_scope();
874 let app_context = Rc::clone(&self.app_context);
875 let _ = app_context.enter(|| {
876 run_in_mutable_snapshot(|| {
877 self.cancel_gesture_inner(event_time);
878 })
879 });
880 }
881
882 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
883 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
884
885 self.hit_path_tracker.clear();
886 self.buttons_pressed = PointerButtons::NONE;
887
888 if !targets.is_empty() {
889 let event = self
890 .pointer_event(
891 PointerEventKind::Cancel,
892 Point {
893 x: self.cursor.0,
894 y: self.cursor.1,
895 },
896 Point {
897 x: self.cursor.0,
898 y: self.cursor.1,
899 },
900 event_time,
901 )
902 .with_source(self.pointer_source);
903
904 self.dispatch_targets(targets, event, false);
905 }
906
907 let pos = Point {
908 x: self.cursor.0,
909 y: self.cursor.1,
910 };
911 let hovered_nodes = self.hovered_nodes.clone();
912 for node_id in hovered_nodes {
913 if let Some(target) = self.renderer.scene().find_target(node_id) {
914 let exit_event = self
915 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
916 .with_source(self.pointer_source);
917 self.dispatch_targets(std::iter::once(target), exit_event, false);
918 }
919 }
920 self.hovered_nodes.clear();
921 cranpose_ui::pointer_icon_session::set_pointer_icon(PointerIcon::DEFAULT);
922 }
923
924 pub fn refresh_pointer_icon(&self) {
933 let app_context = Rc::clone(&self.app_context);
934 app_context.enter(cranpose_ui::pointer_icon_session::refresh_pointer_icon);
935 }
936
937 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
938 let app_context = Rc::clone(&self.app_context);
939 app_context.enter(cranpose_ui::pointer_icon_session::take_pointer_icon_change)
940 }
941
942 fn apply_hovered_pointer_icon(
948 &self,
949 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
950 ) {
951 let icon = hits
952 .iter()
953 .find_map(HitTestTarget::pointer_icon)
954 .unwrap_or(PointerIcon::DEFAULT);
955 cranpose_ui::pointer_icon_session::set_pointer_icon(icon);
956 }
957
958 pub fn set_platform_text_input(
965 &mut self,
966 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
967 ) {
968 let app_context = Rc::clone(&self.app_context);
969 app_context
970 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
971 }
972
973 pub fn notify_app_paused(&mut self) {
980 let app_context = Rc::clone(&self.app_context);
981 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
982 }
983
984 pub fn notify_app_resumed(&mut self) -> bool {
992 let app_context = Rc::clone(&self.app_context);
993 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
994 }
995
996 fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
1005 if !plain_key_down(event) || event.key_code != KeyCode::Tab {
1006 return false;
1007 }
1008 let direction = if event.modifiers.shift {
1009 FocusDirection::Previous
1010 } else {
1011 FocusDirection::Next
1012 };
1013 self.move_focus_in_context(direction)
1014 }
1015
1016 fn on_activation_key(&mut self, event: &KeyEvent) -> bool {
1017 if !plain_key_down(event)
1018 || !matches!(event.key_code, KeyCode::Enter | KeyCode::Space)
1019 || cranpose_ui::text_field_focus::has_focused_field()
1020 {
1021 return false;
1022 }
1023 let Some(focused) = cranpose_ui::active_focus_target() else {
1024 return false;
1025 };
1026 let center = self.with_layout_tree(|layout_tree| {
1027 layout_tree
1028 .map(cranpose_ui::collect_focus_order)
1029 .unwrap_or_default()
1030 .iter()
1031 .find(|entry| entry.node_id == focused)
1032 .map(cranpose_ui::FocusEntry::center)
1033 });
1034 let Some((x, y)) = center else {
1035 return false;
1036 };
1037 self.set_cursor(x, y);
1038 let pressed = self.pointer_pressed();
1039 let released = self.pointer_released_at_position(x, y);
1040 self.note_focus_moved_by_keyboard(true);
1041 pressed || released
1042 }
1043
1044 fn on_arrow_key(&mut self, event: &KeyEvent) -> bool {
1045 if !plain_key_down(event) || cranpose_ui::text_field_focus::has_focused_field() {
1046 return false;
1047 }
1048 let direction = match event.key_code {
1049 KeyCode::ArrowLeft => FocusDirection::Left,
1050 KeyCode::ArrowRight => FocusDirection::Right,
1051 KeyCode::ArrowUp => FocusDirection::Up,
1052 KeyCode::ArrowDown => FocusDirection::Down,
1053 _ => return false,
1054 };
1055 let Some(focused) = cranpose_ui::active_focus_target() else {
1056 return false;
1057 };
1058 let order = self.with_layout_tree(|layout_tree| {
1059 let layout_tree = layout_tree?;
1060 let group = cranpose_ui::selectable_group_of(layout_tree, focused)?;
1061 Some(cranpose_ui::collect_focus_order_under(layout_tree, group))
1062 });
1063 order.is_some_and(|order| self.move_focus_in_order(order, direction))
1064 }
1065
1066 fn note_focus_moved_by_keyboard(&mut self, keyboard: bool) {
1067 if cranpose_ui::set_keyboard_focus_visible(keyboard) {
1068 cranpose_ui::request_render_invalidation();
1069 self.mark_dirty();
1070 }
1071 }
1072
1073 fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
1077 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
1078 return false;
1079 }
1080 self.dismiss_top_modal_in_context()
1081 }
1082
1083 pub fn dismiss_top_modal(&mut self) -> bool {
1086 let _event_handler = enter_event_handler_scope();
1087 let app_context = Rc::clone(&self.app_context);
1088 app_context.enter(|| self.dismiss_top_modal_in_context())
1089 }
1090
1091 fn dismiss_top_modal_in_context(&mut self) -> bool {
1092 let closed = run_in_mutable_snapshot(|| {
1093 if cranpose_ui::modal_depth() > 0 {
1094 cranpose_ui::dispatch_modal_back()
1095 } else {
1096 cranpose_ui::dismiss_top_popup()
1097 }
1098 })
1099 .unwrap_or(false);
1100 if closed {
1101 self.mark_dirty();
1102 }
1103 closed
1104 }
1105
1106 pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
1109 let order = self.with_layout_tree(|layout_tree| {
1110 layout_tree
1111 .map(cranpose_ui::collect_focus_order)
1112 .unwrap_or_default()
1113 });
1114 self.move_focus_in_order(order, direction)
1115 }
1116
1117 fn move_focus_in_order(
1118 &mut self,
1119 order: Vec<cranpose_ui::FocusEntry>,
1120 direction: FocusDirection,
1121 ) -> bool {
1122 cranpose_ui::set_focus_order(order);
1123 let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
1124 .unwrap_or(false);
1125 if moved {
1126 self.mark_dirty();
1127 self.note_focus_moved_by_keyboard(true);
1128 }
1129 moved
1130 }
1131
1132 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1133 let _event_handler = enter_event_handler_scope();
1134 let app_context = Rc::clone(&self.app_context);
1135 app_context.enter(|| self.on_key_event_inner(event))
1136 }
1137
1138 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1139 use KeyEventType::KeyDown;
1140
1141 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1142 #[cfg(all(
1143 feature = "clipboard-native",
1144 not(target_arch = "wasm32"),
1145 not(target_os = "android"),
1146 not(target_os = "ios")
1147 ))]
1148 {
1149 match event.key_code {
1150 KeyCode::C => {
1151 if let Some(text) = self.on_copy_inner() {
1152 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1153 return true;
1154 }
1155 }
1156 KeyCode::X => {
1157 if let Some(text) = self.on_cut_inner() {
1158 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1159 self.mark_dirty();
1160 self.request_layout_pass();
1161 return true;
1162 }
1163 }
1164 KeyCode::V => {
1165 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1166 && self.on_paste_inner(&text)
1167 {
1168 return true;
1169 }
1170 }
1171 _ => {}
1172 }
1173 }
1174 }
1175
1176 if self.on_focus_key(event) {
1177 return true;
1178 }
1179
1180 if self.on_escape_key(event) {
1181 return true;
1182 }
1183
1184 if self.on_activation_key(event) || self.on_arrow_key(event) {
1185 return true;
1186 }
1187
1188 if !cranpose_ui::text_field_focus::has_focused_field() {
1189 return false;
1190 }
1191
1192 let handled =
1193 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1194 .unwrap_or(false);
1195
1196 if handled {
1197 self.mark_dirty();
1198 self.request_layout_pass();
1199 }
1200
1201 handled
1202 }
1203
1204 pub fn on_paste(&mut self, text: &str) -> bool {
1208 let _event_handler = enter_event_handler_scope();
1209 let app_context = Rc::clone(&self.app_context);
1210 app_context.enter(|| self.on_paste_inner(text))
1211 }
1212
1213 fn on_paste_inner(&mut self, text: &str) -> bool {
1214 let handled =
1215 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1216 .unwrap_or(false);
1217
1218 if handled {
1219 self.mark_dirty();
1220 self.request_layout_pass();
1221 }
1222
1223 handled
1224 }
1225
1226 pub fn on_copy(&mut self) -> Option<String> {
1230 let app_context = Rc::clone(&self.app_context);
1231 app_context.enter(|| self.on_copy_inner())
1232 }
1233
1234 fn on_copy_inner(&mut self) -> Option<String> {
1235 cranpose_ui::text_field_focus::dispatch_copy()
1236 }
1237
1238 pub fn on_cut(&mut self) -> Option<String> {
1242 let _event_handler = enter_event_handler_scope();
1243 let app_context = Rc::clone(&self.app_context);
1244 app_context.enter(|| self.on_cut_inner())
1245 }
1246
1247 fn on_cut_inner(&mut self) -> Option<String> {
1248 let text =
1249 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1250
1251 if text.is_some() {
1252 self.mark_dirty();
1253 self.request_layout_pass();
1254 }
1255
1256 text
1257 }
1258
1259 #[cfg(all(
1263 feature = "clipboard-native",
1264 target_os = "linux",
1265 not(target_arch = "wasm32")
1266 ))]
1267 pub fn set_primary_selection(&mut self, text: &str) {
1268 use arboard::{LinuxClipboardKind, SetExtLinux};
1269 if let Some(ref mut clipboard) = self.clipboard {
1270 let result = clipboard
1271 .set()
1272 .clipboard(LinuxClipboardKind::Primary)
1273 .text(text.to_string());
1274 if let Err(e) = result {
1275 log::debug!("Primary selection set failed: {:?}", e);
1276 }
1277 }
1278 }
1279
1280 #[cfg(not(all(
1281 feature = "clipboard-native",
1282 target_os = "linux",
1283 not(target_arch = "wasm32")
1284 )))]
1285 pub fn set_primary_selection(&mut self, _text: &str) {}
1286
1287 #[cfg(all(
1290 feature = "clipboard-native",
1291 target_os = "linux",
1292 not(target_arch = "wasm32")
1293 ))]
1294 pub fn get_primary_selection(&mut self) -> Option<String> {
1295 use arboard::{GetExtLinux, LinuxClipboardKind};
1296 if let Some(ref mut clipboard) = self.clipboard {
1297 clipboard
1298 .get()
1299 .clipboard(LinuxClipboardKind::Primary)
1300 .text()
1301 .ok()
1302 } else {
1303 None
1304 }
1305 }
1306
1307 #[cfg(not(all(
1308 feature = "clipboard-native",
1309 target_os = "linux",
1310 not(target_arch = "wasm32")
1311 )))]
1312 pub fn get_primary_selection(&mut self) -> Option<String> {
1313 None
1314 }
1315
1316 pub fn sync_selection_to_primary(&mut self) {
1319 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1320 {
1321 if let Some(text) = self.on_copy() {
1322 self.set_primary_selection(&text);
1323 }
1324 }
1325 }
1326
1327 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1335 let _event_handler = enter_event_handler_scope();
1336 let app_context = Rc::clone(&self.app_context);
1337 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1338 }
1339
1340 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1341 let handled = run_in_mutable_snapshot(|| {
1342 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1343 })
1344 .unwrap_or(false);
1345
1346 if handled {
1347 self.mark_dirty();
1348 self.request_layout_pass();
1349 }
1350
1351 handled
1352 }
1353
1354 pub fn on_ime_finish_composing(&mut self) -> bool {
1358 let _event_handler = enter_event_handler_scope();
1359 let app_context = Rc::clone(&self.app_context);
1360 app_context.enter(|| self.on_ime_finish_composing_inner())
1361 }
1362
1363 fn on_ime_finish_composing_inner(&mut self) -> bool {
1364 let handled =
1365 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1366 .unwrap_or(false);
1367
1368 if handled {
1369 self.mark_dirty();
1370 self.request_layout_pass();
1371 }
1372
1373 handled
1374 }
1375
1376 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1380 let _event_handler = enter_event_handler_scope();
1381 let app_context = Rc::clone(&self.app_context);
1382 app_context.enter(|| {
1383 let handled = run_in_mutable_snapshot(|| {
1384 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1385 start_bytes,
1386 end_bytes,
1387 )
1388 })
1389 .unwrap_or(false);
1390
1391 if handled {
1392 self.mark_dirty();
1393 self.request_layout_pass();
1394 }
1395
1396 handled
1397 })
1398 }
1399
1400 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1405 let _event_handler = enter_event_handler_scope();
1406 let app_context = Rc::clone(&self.app_context);
1407 app_context.enter(|| {
1408 let handled = run_in_mutable_snapshot(|| {
1409 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1410 })
1411 .unwrap_or(false);
1412
1413 if handled {
1414 self.mark_dirty();
1415 }
1416
1417 handled
1418 })
1419 }
1420
1421 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1425 let app_context = Rc::clone(&self.app_context);
1426 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1427 }
1428
1429 pub fn ime_caret_geometry(
1433 &mut self,
1434 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1435 let app_context = Rc::clone(&self.app_context);
1436 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1437 }
1438
1439 pub fn clear_text_field_focus(&mut self) {
1442 let _event_handler = enter_event_handler_scope();
1443 let app_context = Rc::clone(&self.app_context);
1444 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1445 self.mark_dirty();
1446 self.request_layout_pass();
1447 }
1448
1449 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1452 let _event_handler = enter_event_handler_scope();
1453 let app_context = Rc::clone(&self.app_context);
1454 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1455 }
1456
1457 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1458 let handled = run_in_mutable_snapshot(|| {
1459 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1460 })
1461 .unwrap_or(false);
1462
1463 if handled {
1464 self.mark_dirty();
1465 self.request_layout_pass();
1466 }
1467
1468 handled
1469 }
1470}
1471
1472fn plain_key_down(event: &KeyEvent) -> bool {
1473 event.event_type == KeyEventType::KeyDown
1474 && !event.modifiers.ctrl
1475 && !event.modifiers.meta
1476 && !event.modifiers.alt
1477}