1use super::*;
2
3impl<R> AppShell<R>
4where
5 R: Renderer,
6 R::Error: Debug,
7{
8 fn pointer_event(
9 &self,
10 kind: PointerEventKind,
11 position: Point,
12 global_position: Point,
13 event_time: PointerEventTime,
14 ) -> PointerEvent {
15 let mut event = PointerEvent::new(kind, position, global_position)
16 .with_time_ms(event_time.platform_time_ms)
17 .with_animation_time_nanos(event_time.animation_time_nanos);
18 event.modifiers = self.modifiers;
24 event
25 }
26
27 fn resolve_gesture_targets(
28 &self,
29 pointer: PointerId,
30 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
31 self.resolve_hit_path(pointer)
32 }
33
34 fn resolve_hit_path(
41 &self,
42 pointer: PointerId,
43 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
44 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
45 return Vec::new();
46 };
47
48 let scene = self.renderer.scene();
49 let targets: Vec<_> = node_ids
50 .iter()
51 .filter_map(|&id| scene.find_target(id))
52 .collect();
53 log::trace!(
54 target: "cranpose::input",
55 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
56 targets.len()
57 );
58 targets
59 }
60
61 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
62 where
63 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
64 {
65 let mut applier = self.composition.applier_mut();
66 for target in targets {
67 let node_id = target.node_id();
68 target.dispatch_with_applier(&mut applier, event.clone());
69 log::trace!(
70 target: "cranpose::input",
71 "dispatch {:?} node={} consumed={} stop_on_consume={}",
72 event.kind,
73 node_id,
74 event.is_consumed(),
75 stop_on_consume,
76 );
77 if stop_on_consume && event.is_consumed() {
78 break;
79 }
80 }
81 event.finish_post_dispatch();
82 }
83
84 pub fn set_pointer_source(&mut self, source: PointerSource) {
90 self.pointer_source = source;
91 }
92
93 pub fn pointer_source(&self) -> PointerSource {
95 self.pointer_source
96 }
97
98 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
107 self.modifiers = Some(modifiers);
108 }
109
110 pub fn modifiers(&self) -> Option<Modifiers> {
114 self.modifiers
115 }
116
117 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
118 self.set_cursor_at_time(x, y, None)
119 }
120
121 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
128 let event_time = self.realtime_pointer_event_time(time_ms);
129 self.set_cursor_at_event_time(x, y, event_time)
130 }
131
132 pub fn set_cursor_at_event_time(
134 &mut self,
135 x: f32,
136 y: f32,
137 event_time: PointerEventTime,
138 ) -> bool {
139 let _event_handler = enter_event_handler_scope();
140 let app_context = Rc::clone(&self.app_context);
141 let result = app_context.enter(|| {
142 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
143 });
144 if result {
145 self.mark_dirty();
146 }
147 log::trace!(
148 target: "cranpose::input",
149 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
150 event_time.platform_time_ms,
151 event_time.animation_time_nanos,
152 );
153 result
154 }
155
156 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
157 self.cursor = (x, y);
158
159 if self.buttons_pressed != PointerButtons::NONE {
163 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
164 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
165 if !targets.is_empty() {
166 let event = self
167 .pointer_event(
168 PointerEventKind::Move,
169 Point { x, y },
170 Point { x, y },
171 event_time,
172 )
173 .with_buttons(self.buttons_pressed)
174 .with_source(self.pointer_source);
175 self.dispatch_targets(targets, event, false);
176 return true;
177 }
178
179 return false;
180 }
181
182 return false;
185 }
186
187 let hits = self.renderer.scene().hit_test(x, y);
190 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
191
192 let pos = Point { x, y };
194 let previously_hovered = self.hovered_nodes.clone();
195 for old_id in previously_hovered {
196 if !new_ids.contains(&old_id)
197 && let Some(target) = self.renderer.scene().find_target(old_id)
198 {
199 let exit_event = self
200 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
201 .with_buttons(self.buttons_pressed)
202 .with_source(self.pointer_source);
203 self.dispatch_targets(std::iter::once(target), exit_event, false);
204 }
205 }
206
207 for hit in &hits {
209 if !self.hovered_nodes.contains(&hit.node_id()) {
210 let enter_event = self
211 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
212 .with_buttons(self.buttons_pressed)
213 .with_source(self.pointer_source);
214 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
215 }
216 }
217
218 self.hovered_nodes = new_ids;
219
220 if !hits.is_empty() {
221 let event = self
222 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
223 .with_buttons(self.buttons_pressed)
224 .with_source(self.pointer_source);
225 self.dispatch_targets(hits, event, true);
226 true
227 } else {
228 false
229 }
230 }
231
232 pub fn pointer_pressed(&mut self) -> bool {
233 self.pointer_pressed_at_time(None)
234 }
235
236 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
239 let event_time = self.realtime_pointer_event_time(time_ms);
240 self.pointer_pressed_at_event_time(event_time)
241 }
242
243 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
245 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
249 return true;
250 }
251 let _event_handler = enter_event_handler_scope();
252 let app_context = Rc::clone(&self.app_context);
253 let result = app_context.enter(|| {
254 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
255 });
256 if result {
257 self.mark_dirty();
258 }
259 log::trace!(
260 target: "cranpose::input",
261 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
262 event_time.platform_time_ms,
263 event_time.animation_time_nanos,
264 );
265 result
266 }
267
268 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
269 self.buttons_pressed.insert(PointerButton::Primary);
271
272 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
280 if hits.is_empty() {
281 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
282 false
283 } else {
284 let event = self
285 .pointer_event(
286 PointerEventKind::Down,
287 Point {
288 x: self.cursor.0,
289 y: self.cursor.1,
290 },
291 Point {
292 x: self.cursor.0,
293 y: self.cursor.1,
294 },
295 event_time,
296 )
297 .with_buttons(self.buttons_pressed)
298 .with_source(self.pointer_source);
299
300 let mut delivered_capture_paths = Vec::new();
301 let mut applier = self.composition.applier_mut();
302 for hit in hits {
303 let node_id = hit.node_id();
304 delivered_capture_paths.push(hit.capture_path());
305 hit.dispatch_with_applier(&mut applier, event.clone());
306 log::trace!(
307 target: "cranpose::input",
308 "dispatch {:?} node={} consumed={} stop_on_consume=true",
309 event.kind,
310 node_id,
311 event.is_consumed(),
312 );
313 if event.is_consumed() {
314 break;
315 }
316 }
317
318 self.hit_path_tracker
319 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
320 log::trace!(
321 target: "cranpose::input",
322 "pointer_pressed_inner cached_hit_path={:?}",
323 self.hit_path_tracker.get_path(PointerId::PRIMARY),
324 );
325
326 true
327 }
328 }
329
330 pub fn pointer_released(&mut self) -> bool {
331 self.pointer_released_at_time(None)
332 }
333
334 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
347 self.pointer_released_at_position_time(x, y, None)
348 }
349
350 pub fn pointer_released_at_position_time(
354 &mut self,
355 x: f32,
356 y: f32,
357 time_ms: Option<i64>,
358 ) -> bool {
359 let event_time = self.realtime_pointer_event_time(time_ms);
360 self.pointer_released_at_position_event_time(x, y, event_time)
361 }
362
363 pub fn pointer_released_at_position_event_time(
365 &mut self,
366 x: f32,
367 y: f32,
368 event_time: PointerEventTime,
369 ) -> bool {
370 let _event_handler = enter_event_handler_scope();
371 let app_context = Rc::clone(&self.app_context);
372 let result = app_context.enter(|| {
373 run_in_mutable_snapshot(|| {
374 self.cursor = (x, y);
375 self.pointer_released_inner(event_time)
376 })
377 .unwrap_or(false)
378 });
379 if result {
380 self.mark_dirty();
381 }
382 log::trace!(
383 target: "cranpose::input",
384 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
385 event_time.platform_time_ms,
386 event_time.animation_time_nanos,
387 );
388 result
389 }
390
391 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
394 let event_time = self.realtime_pointer_event_time(time_ms);
395 self.pointer_released_at_event_time(event_time)
396 }
397
398 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
400 let _event_handler = enter_event_handler_scope();
401 let app_context = Rc::clone(&self.app_context);
402 let result = app_context.enter(|| {
403 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
404 });
405 if result {
406 self.mark_dirty();
407 }
408 log::trace!(
409 target: "cranpose::input",
410 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
411 event_time.platform_time_ms,
412 event_time.animation_time_nanos,
413 );
414 result
415 }
416
417 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
418 self.buttons_pressed.remove(PointerButton::Primary);
421 let corrected_buttons = self.buttons_pressed;
422 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
423
424 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 let capture_paths = hits
588 .iter()
589 .map(|hit| hit.capture_path())
590 .collect::<Vec<_>>();
591 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
592 .into_iter()
593 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
594 .collect::<Vec<_>>();
595
596 self.dispatch_targets(targets, event.clone(), true);
597
598 event.is_consumed()
599 }
600
601 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
626 if wheel.is_zoom() {
627 let zoom_factor = wheel.zoom_factor();
628 log::trace!(
629 target: "cranpose::input",
630 "wheel zoom factor={zoom_factor:.4}"
631 );
632 return self.pointer_zoomed(zoom_factor);
633 }
634
635 let rotary =
636 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
637 if self.rotary_scrolled(rotary) {
638 return true;
639 }
640
641 let delta = wheel.scroll_delta();
642 log::trace!(
643 target: "cranpose::input",
644 "wheel delta ({:.2},{:.2}) alt={}",
645 delta.x,
646 delta.y,
647 wheel.modifiers.alt
648 );
649 self.pointer_scrolled(delta.x, delta.y)
650 }
651
652 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
660 let event_time = self.realtime_pointer_event_time(None);
661 let _event_handler = enter_event_handler_scope();
662 let app_context = Rc::clone(&self.app_context);
663 let result = app_context.enter(|| {
664 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
665 .unwrap_or(false)
666 });
667 if result {
668 self.mark_dirty();
669 }
670 log::trace!(
671 target: "cranpose::input",
672 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
673 );
674 result
675 }
676
677 fn pointer_scrolled_inner(
678 &mut self,
679 delta_x: f32,
680 delta_y: f32,
681 event_time: PointerEventTime,
682 ) -> bool {
683 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
684 return false;
685 }
686
687 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
688 if hits.is_empty() {
689 return false;
690 }
691
692 let event = self
693 .pointer_event(
694 PointerEventKind::Scroll,
695 Point {
696 x: self.cursor.0,
697 y: self.cursor.1,
698 },
699 Point {
700 x: self.cursor.0,
701 y: self.cursor.1,
702 },
703 event_time,
704 )
705 .with_buttons(self.buttons_pressed)
706 .with_scroll_delta(Point {
707 x: delta_x,
708 y: delta_y,
709 })
710 .with_source(self.pointer_source);
711
712 let capture_paths = hits
713 .iter()
714 .map(|hit| hit.capture_path())
715 .collect::<Vec<_>>();
716 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
717 .into_iter()
718 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
719 .collect::<Vec<_>>();
720
721 self.dispatch_targets(targets, event.clone(), true);
722
723 event.is_consumed()
724 }
725
726 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
737 where
738 F: Fn(RotaryScrollEvent) -> bool + 'static,
739 {
740 self.on_rotary_scroll = Some(Rc::new(handler));
741 }
742
743 pub fn clear_on_rotary_scroll(&mut self) {
745 self.on_rotary_scroll = None;
746 }
747
748 pub fn rotary_scroll_factor(&self) -> f32 {
751 self.rotary_scroll_factor
752 }
753
754 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
763 if factor.is_finite() && factor > 0.0 {
764 self.rotary_scroll_factor = factor;
765 }
766 }
767
768 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
774 let factor = self.rotary_scroll_factor;
775 self.rotary_scrolled(RotaryScrollEvent::from_detents(
776 detents,
777 factor,
778 factor,
779 uptime_millis,
780 ))
781 }
782
783 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
804 let _event_handler = enter_event_handler_scope();
805 let app_context = Rc::clone(&self.app_context);
806 let result = app_context.enter(|| {
807 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
808 });
809 if result {
810 self.mark_dirty();
811 }
812 log::trace!(
813 target: "cranpose::input",
814 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
815 event.vertical_scroll_pixels,
816 event.horizontal_scroll_pixels,
817 event.uptime_millis,
818 );
819 result
820 }
821
822 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
823 if rotary.is_empty() {
824 return false;
825 }
826
827 let bubble_order = self.rotary_dispatch_order();
830 let position = Point {
831 x: self.cursor.0,
832 y: self.cursor.1,
833 };
834
835 if !bubble_order.is_empty() {
836 let capture_targets = bubble_order
838 .iter()
839 .rev()
840 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
841 .collect::<Vec<_>>();
842 let mut capture_event =
843 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
844 capture_event.modifiers = self.modifiers;
845 self.dispatch_targets(capture_targets, capture_event.clone(), true);
846 if capture_event.is_consumed() {
847 return true;
848 }
849
850 let bubble_targets = bubble_order
852 .iter()
853 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
854 .collect::<Vec<_>>();
855 let mut bubble_event =
856 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
857 bubble_event.modifiers = self.modifiers;
858 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
859 if bubble_event.is_consumed() {
860 return true;
861 }
862 }
863
864 if let Some(handler) = self.on_rotary_scroll.clone() {
866 return handler(rotary);
867 }
868
869 false
870 }
871
872 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
878 if let Some(focused) = cranpose_ui::active_focus_target()
879 && let Some(target) = self.renderer.scene().find_target(focused)
880 {
881 let path = target.capture_path();
882 if !path.is_empty() {
883 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
884 }
885 }
886
887 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
888 if hits.is_empty() {
889 return Vec::new();
890 }
891 let capture_paths = hits
892 .iter()
893 .map(|hit| hit.capture_path())
894 .collect::<Vec<_>>();
895 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
896 }
897
898 pub fn cancel_gesture(&mut self) {
904 let event_time = self.realtime_pointer_event_time(None);
905 let _event_handler = enter_event_handler_scope();
906 let app_context = Rc::clone(&self.app_context);
907 let _ = app_context.enter(|| {
908 run_in_mutable_snapshot(|| {
909 self.cancel_gesture_inner(event_time);
910 })
911 });
912 }
913
914 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
915 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
916
917 self.hit_path_tracker.clear();
919 self.buttons_pressed = PointerButtons::NONE;
920
921 if !targets.is_empty() {
922 let event = self
923 .pointer_event(
924 PointerEventKind::Cancel,
925 Point {
926 x: self.cursor.0,
927 y: self.cursor.1,
928 },
929 Point {
930 x: self.cursor.0,
931 y: self.cursor.1,
932 },
933 event_time,
934 )
935 .with_source(self.pointer_source);
936
937 self.dispatch_targets(targets, event, false);
938 }
939
940 let pos = Point {
942 x: self.cursor.0,
943 y: self.cursor.1,
944 };
945 let hovered_nodes = self.hovered_nodes.clone();
946 for node_id in hovered_nodes {
947 if let Some(target) = self.renderer.scene().find_target(node_id) {
948 let exit_event = self
949 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
950 .with_source(self.pointer_source);
951 self.dispatch_targets(std::iter::once(target), exit_event, false);
952 }
953 }
954 self.hovered_nodes.clear();
955 }
956
957 pub fn set_platform_text_input(
964 &mut self,
965 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
966 ) {
967 let app_context = Rc::clone(&self.app_context);
968 app_context
969 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
970 }
971
972 pub fn notify_app_paused(&mut self) {
979 let app_context = Rc::clone(&self.app_context);
980 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
981 }
982
983 pub fn notify_app_resumed(&mut self) -> bool {
991 let app_context = Rc::clone(&self.app_context);
992 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
993 }
994
995 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1002 let _event_handler = enter_event_handler_scope();
1003 let app_context = Rc::clone(&self.app_context);
1004 app_context.enter(|| self.on_key_event_inner(event))
1005 }
1006
1007 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1009 use KeyEventType::KeyDown;
1010
1011 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1013 #[cfg(all(
1014 feature = "clipboard-native",
1015 not(target_arch = "wasm32"),
1016 not(target_os = "android"),
1017 not(target_os = "ios")
1018 ))]
1019 {
1020 match event.key_code {
1021 KeyCode::C => {
1023 if let Some(text) = self.on_copy_inner() {
1024 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1025 return true;
1026 }
1027 }
1028 KeyCode::X => {
1030 if let Some(text) = self.on_cut_inner() {
1031 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1032 self.mark_dirty();
1033 self.request_layout_pass();
1034 return true;
1035 }
1036 }
1037 KeyCode::V => {
1039 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1040 && self.on_paste_inner(&text)
1041 {
1042 return true;
1043 }
1044 }
1045 _ => {}
1046 }
1047 }
1048 }
1049
1050 if !cranpose_ui::text_field_focus::has_focused_field() {
1052 return false;
1053 }
1054
1055 let handled = run_in_mutable_snapshot(|| {
1059 cranpose_ui::text_field_focus::dispatch_key_event(event)
1062 })
1063 .unwrap_or(false);
1064
1065 if handled {
1066 self.mark_dirty();
1068 self.request_layout_pass();
1069 }
1070
1071 handled
1072 }
1073
1074 pub fn on_paste(&mut self, text: &str) -> bool {
1078 let _event_handler = enter_event_handler_scope();
1079 let app_context = Rc::clone(&self.app_context);
1080 app_context.enter(|| self.on_paste_inner(text))
1081 }
1082
1083 fn on_paste_inner(&mut self, text: &str) -> bool {
1084 let handled =
1088 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1089 .unwrap_or(false);
1090
1091 if handled {
1092 self.mark_dirty();
1093 self.request_layout_pass();
1094 }
1095
1096 handled
1097 }
1098
1099 pub fn on_copy(&mut self) -> Option<String> {
1103 let app_context = Rc::clone(&self.app_context);
1104 app_context.enter(|| self.on_copy_inner())
1105 }
1106
1107 fn on_copy_inner(&mut self) -> Option<String> {
1108 cranpose_ui::text_field_focus::dispatch_copy()
1110 }
1111
1112 pub fn on_cut(&mut self) -> Option<String> {
1116 let _event_handler = enter_event_handler_scope();
1117 let app_context = Rc::clone(&self.app_context);
1118 app_context.enter(|| self.on_cut_inner())
1119 }
1120
1121 fn on_cut_inner(&mut self) -> Option<String> {
1122 let text =
1123 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1124
1125 if text.is_some() {
1126 self.mark_dirty();
1127 self.request_layout_pass();
1128 }
1129
1130 text
1131 }
1132
1133 #[cfg(all(
1137 feature = "clipboard-native",
1138 target_os = "linux",
1139 not(target_arch = "wasm32")
1140 ))]
1141 pub fn set_primary_selection(&mut self, text: &str) {
1142 use arboard::{LinuxClipboardKind, SetExtLinux};
1143 if let Some(ref mut clipboard) = self.clipboard {
1144 let result = clipboard
1145 .set()
1146 .clipboard(LinuxClipboardKind::Primary)
1147 .text(text.to_string());
1148 if let Err(e) = result {
1149 log::debug!("Primary selection set failed: {:?}", e);
1151 }
1152 }
1153 }
1154
1155 #[cfg(not(all(
1156 feature = "clipboard-native",
1157 target_os = "linux",
1158 not(target_arch = "wasm32")
1159 )))]
1160 pub fn set_primary_selection(&mut self, _text: &str) {}
1161
1162 #[cfg(all(
1165 feature = "clipboard-native",
1166 target_os = "linux",
1167 not(target_arch = "wasm32")
1168 ))]
1169 pub fn get_primary_selection(&mut self) -> Option<String> {
1170 use arboard::{GetExtLinux, LinuxClipboardKind};
1171 if let Some(ref mut clipboard) = self.clipboard {
1172 clipboard
1173 .get()
1174 .clipboard(LinuxClipboardKind::Primary)
1175 .text()
1176 .ok()
1177 } else {
1178 None
1179 }
1180 }
1181
1182 #[cfg(not(all(
1183 feature = "clipboard-native",
1184 target_os = "linux",
1185 not(target_arch = "wasm32")
1186 )))]
1187 pub fn get_primary_selection(&mut self) -> Option<String> {
1188 None
1189 }
1190
1191 pub fn sync_selection_to_primary(&mut self) {
1194 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1195 {
1196 if let Some(text) = self.on_copy() {
1197 self.set_primary_selection(&text);
1198 }
1199 }
1200 }
1201
1202 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1210 let _event_handler = enter_event_handler_scope();
1211 let app_context = Rc::clone(&self.app_context);
1212 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1213 }
1214
1215 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1216 let handled = run_in_mutable_snapshot(|| {
1218 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1219 })
1220 .unwrap_or(false);
1221
1222 if handled {
1223 self.mark_dirty();
1224 self.request_layout_pass();
1226 }
1227
1228 handled
1229 }
1230
1231 pub fn on_ime_finish_composing(&mut self) -> bool {
1235 let _event_handler = enter_event_handler_scope();
1236 let app_context = Rc::clone(&self.app_context);
1237 app_context.enter(|| self.on_ime_finish_composing_inner())
1238 }
1239
1240 fn on_ime_finish_composing_inner(&mut self) -> bool {
1241 let handled =
1242 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1243 .unwrap_or(false);
1244
1245 if handled {
1246 self.mark_dirty();
1247 self.request_layout_pass();
1248 }
1249
1250 handled
1251 }
1252
1253 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1257 let _event_handler = enter_event_handler_scope();
1258 let app_context = Rc::clone(&self.app_context);
1259 app_context.enter(|| {
1260 let handled = run_in_mutable_snapshot(|| {
1261 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1262 start_bytes,
1263 end_bytes,
1264 )
1265 })
1266 .unwrap_or(false);
1267
1268 if handled {
1269 self.mark_dirty();
1270 self.request_layout_pass();
1271 }
1272
1273 handled
1274 })
1275 }
1276
1277 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1282 let _event_handler = enter_event_handler_scope();
1283 let app_context = Rc::clone(&self.app_context);
1284 app_context.enter(|| {
1285 let handled = run_in_mutable_snapshot(|| {
1286 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1287 })
1288 .unwrap_or(false);
1289
1290 if handled {
1293 self.mark_dirty();
1294 }
1295
1296 handled
1297 })
1298 }
1299
1300 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1304 let app_context = Rc::clone(&self.app_context);
1305 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1306 }
1307
1308 pub fn ime_caret_geometry(
1312 &mut self,
1313 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1314 let app_context = Rc::clone(&self.app_context);
1315 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1316 }
1317
1318 pub fn clear_text_field_focus(&mut self) {
1321 let _event_handler = enter_event_handler_scope();
1322 let app_context = Rc::clone(&self.app_context);
1323 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1324 self.mark_dirty();
1325 self.request_layout_pass();
1326 }
1327
1328 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1331 let _event_handler = enter_event_handler_scope();
1332 let app_context = Rc::clone(&self.app_context);
1333 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1334 }
1335
1336 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1337 let handled = run_in_mutable_snapshot(|| {
1338 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1339 })
1340 .unwrap_or(false);
1341
1342 if handled {
1343 self.mark_dirty();
1344 self.request_layout_pass();
1345 }
1346
1347 handled
1348 }
1349}