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;
19 event
20 }
21
22 fn resolve_gesture_targets(
23 &self,
24 pointer: PointerId,
25 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
26 self.resolve_hit_path(pointer)
27 }
28
29 fn resolve_hit_path(
30 &self,
31 pointer: PointerId,
32 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
33 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
34 return Vec::new();
35 };
36
37 let scene = self.renderer.scene();
38 let targets: Vec<_> = node_ids
39 .iter()
40 .filter_map(|&id| scene.find_target(id))
41 .collect();
42 log::trace!(
43 target: "cranpose::input",
44 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
45 targets.len()
46 );
47 targets
48 }
49
50 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
51 where
52 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
53 {
54 let mut applier = self.composition.applier_mut();
55 for target in targets {
56 let node_id = target.node_id();
57 target.dispatch_with_applier(&mut applier, event.clone());
58 log::trace!(
59 target: "cranpose::input",
60 "dispatch {:?} node={} consumed={} stop_on_consume={}",
61 event.kind,
62 node_id,
63 event.is_consumed(),
64 stop_on_consume,
65 );
66 if stop_on_consume && event.is_consumed() {
67 break;
68 }
69 }
70 event.finish_post_dispatch();
71 }
72
73 pub fn set_pointer_source(&mut self, source: PointerSource) {
79 self.pointer_source = source;
80 }
81
82 pub fn pointer_source(&self) -> PointerSource {
84 self.pointer_source
85 }
86
87 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
96 self.modifiers = Some(modifiers);
97 }
98
99 pub fn modifiers(&self) -> Option<Modifiers> {
103 self.modifiers
104 }
105
106 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
107 self.set_cursor_at_time(x, y, None)
108 }
109
110 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
117 let event_time = self.realtime_pointer_event_time(time_ms);
118 self.set_cursor_at_event_time(x, y, event_time)
119 }
120
121 pub fn set_cursor_at_event_time(
123 &mut self,
124 x: f32,
125 y: f32,
126 event_time: PointerEventTime,
127 ) -> bool {
128 let _event_handler = enter_event_handler_scope();
129 let app_context = Rc::clone(&self.app_context);
130 let result = app_context.enter(|| {
131 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
132 });
133 if result {
134 self.mark_dirty();
135 }
136 log::trace!(
137 target: "cranpose::input",
138 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
139 event_time.platform_time_ms,
140 event_time.animation_time_nanos,
141 );
142 result
143 }
144
145 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
146 self.cursor = (x, y);
147
148 if self.buttons_pressed != PointerButtons::NONE {
149 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
150 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
151 if !targets.is_empty() {
152 let event = self
153 .pointer_event(
154 PointerEventKind::Move,
155 Point { x, y },
156 Point { x, y },
157 event_time,
158 )
159 .with_buttons(self.buttons_pressed)
160 .with_source(self.pointer_source);
161 self.dispatch_targets(targets, event, false);
162 return true;
163 }
164
165 return false;
166 }
167
168 return false;
169 }
170
171 let hits = self.renderer.scene().hit_test(x, y);
172 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
173
174 let pos = Point { x, y };
175 let previously_hovered = self.hovered_nodes.clone();
176 for old_id in previously_hovered {
177 if !new_ids.contains(&old_id)
178 && let Some(target) = self.renderer.scene().find_target(old_id)
179 {
180 let exit_event = self
181 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
182 .with_buttons(self.buttons_pressed)
183 .with_source(self.pointer_source);
184 self.dispatch_targets(std::iter::once(target), exit_event, false);
185 }
186 }
187
188 for hit in &hits {
189 if !self.hovered_nodes.contains(&hit.node_id()) {
190 let enter_event = self
191 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
192 .with_buttons(self.buttons_pressed)
193 .with_source(self.pointer_source);
194 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
195 }
196 }
197
198 self.hovered_nodes = new_ids;
199
200 if !hits.is_empty() {
201 let event = self
202 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
203 .with_buttons(self.buttons_pressed)
204 .with_source(self.pointer_source);
205 self.dispatch_targets(hits, event, true);
206 true
207 } else {
208 false
209 }
210 }
211
212 pub fn pointer_pressed(&mut self) -> bool {
213 self.pointer_pressed_at_time(None)
214 }
215
216 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
219 let event_time = self.realtime_pointer_event_time(time_ms);
220 self.pointer_pressed_at_event_time(event_time)
221 }
222
223 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
225 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
226 return true;
227 }
228 let _event_handler = enter_event_handler_scope();
229 let app_context = Rc::clone(&self.app_context);
230 let result = app_context.enter(|| {
231 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
232 });
233 if result {
234 self.mark_dirty();
235 }
236 log::trace!(
237 target: "cranpose::input",
238 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
239 event_time.platform_time_ms,
240 event_time.animation_time_nanos,
241 );
242 result
243 }
244
245 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
246 self.buttons_pressed.insert(PointerButton::Primary);
247
248 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
249 if hits.is_empty() {
250 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
251 false
252 } else {
253 let event = self
254 .pointer_event(
255 PointerEventKind::Down,
256 Point {
257 x: self.cursor.0,
258 y: self.cursor.1,
259 },
260 Point {
261 x: self.cursor.0,
262 y: self.cursor.1,
263 },
264 event_time,
265 )
266 .with_buttons(self.buttons_pressed)
267 .with_source(self.pointer_source);
268
269 let mut delivered_capture_paths = Vec::new();
270 let mut applier = self.composition.applier_mut();
271 for hit in hits {
272 let node_id = hit.node_id();
273 delivered_capture_paths.push(hit.capture_path());
274 hit.dispatch_with_applier(&mut applier, event.clone());
275 log::trace!(
276 target: "cranpose::input",
277 "dispatch {:?} node={} consumed={} stop_on_consume=true",
278 event.kind,
279 node_id,
280 event.is_consumed(),
281 );
282 if event.is_consumed() {
283 break;
284 }
285 }
286
287 self.hit_path_tracker
288 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
289 log::trace!(
290 target: "cranpose::input",
291 "pointer_pressed_inner cached_hit_path={:?}",
292 self.hit_path_tracker.get_path(PointerId::PRIMARY),
293 );
294
295 true
296 }
297 }
298
299 pub fn pointer_released(&mut self) -> bool {
300 self.pointer_released_at_time(None)
301 }
302
303 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
316 self.pointer_released_at_position_time(x, y, None)
317 }
318
319 pub fn pointer_released_at_position_time(
323 &mut self,
324 x: f32,
325 y: f32,
326 time_ms: Option<i64>,
327 ) -> bool {
328 let event_time = self.realtime_pointer_event_time(time_ms);
329 self.pointer_released_at_position_event_time(x, y, event_time)
330 }
331
332 pub fn pointer_released_at_position_event_time(
334 &mut self,
335 x: f32,
336 y: f32,
337 event_time: PointerEventTime,
338 ) -> bool {
339 let _event_handler = enter_event_handler_scope();
340 let app_context = Rc::clone(&self.app_context);
341 let result = app_context.enter(|| {
342 run_in_mutable_snapshot(|| {
343 self.cursor = (x, y);
344 self.pointer_released_inner(event_time)
345 })
346 .unwrap_or(false)
347 });
348 if result {
349 self.mark_dirty();
350 }
351 log::trace!(
352 target: "cranpose::input",
353 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
354 event_time.platform_time_ms,
355 event_time.animation_time_nanos,
356 );
357 result
358 }
359
360 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
363 let event_time = self.realtime_pointer_event_time(time_ms);
364 self.pointer_released_at_event_time(event_time)
365 }
366
367 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
369 let _event_handler = enter_event_handler_scope();
370 let app_context = Rc::clone(&self.app_context);
371 let result = app_context.enter(|| {
372 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
373 });
374 if result {
375 self.mark_dirty();
376 }
377 log::trace!(
378 target: "cranpose::input",
379 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
380 event_time.platform_time_ms,
381 event_time.animation_time_nanos,
382 );
383 result
384 }
385
386 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
387 self.buttons_pressed.remove(PointerButton::Primary);
388 let corrected_buttons = self.buttons_pressed;
389 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
390
391 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
392
393 if !targets.is_empty() {
394 let event = self
395 .pointer_event(
396 PointerEventKind::Up,
397 Point {
398 x: self.cursor.0,
399 y: self.cursor.1,
400 },
401 Point {
402 x: self.cursor.0,
403 y: self.cursor.1,
404 },
405 event_time,
406 )
407 .with_buttons(corrected_buttons)
408 .with_source(self.pointer_source);
409
410 self.dispatch_targets(targets, event, false);
411 true
412 } else {
413 false
414 }
415 }
416
417 pub fn secondary_pointer_pressed(
426 &mut self,
427 pointer_id: u64,
428 x: f32,
429 y: f32,
430 time_ms: Option<i64>,
431 ) -> bool {
432 let event_time = self.realtime_pointer_event_time(time_ms);
433 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
434 }
435
436 pub fn secondary_pointer_moved(
438 &mut self,
439 pointer_id: u64,
440 x: f32,
441 y: f32,
442 time_ms: Option<i64>,
443 ) -> bool {
444 let event_time = self.realtime_pointer_event_time(time_ms);
445 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
446 }
447
448 pub fn secondary_pointer_released(
450 &mut self,
451 pointer_id: u64,
452 x: f32,
453 y: f32,
454 time_ms: Option<i64>,
455 ) -> bool {
456 let event_time = self.realtime_pointer_event_time(time_ms);
457 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
458 }
459
460 fn dispatch_secondary_pointer(
461 &mut self,
462 kind: PointerEventKind,
463 pointer_id: u64,
464 x: f32,
465 y: f32,
466 event_time: PointerEventTime,
467 ) -> bool {
468 if pointer_id == 0 {
469 log::warn!(
470 target: "cranpose::input",
471 "secondary pointer dispatch called with the primary pointer id"
472 );
473 return false;
474 }
475
476 let _event_handler = enter_event_handler_scope();
477 let app_context = Rc::clone(&self.app_context);
478 let result = app_context.enter(|| {
479 run_in_mutable_snapshot(|| {
480 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
481 return false;
482 }
483 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
484 if targets.is_empty() {
485 return false;
486 }
487 let pos = Point { x, y };
488 let event = self
489 .pointer_event(kind, pos, pos, event_time)
490 .with_buttons(self.buttons_pressed)
491 .with_id(pointer_id)
492 .with_source(self.pointer_source);
493 self.dispatch_targets(targets, event, false);
494 true
495 })
496 .unwrap_or(false)
497 });
498 if result {
499 self.mark_dirty();
500 }
501 log::trace!(
502 target: "cranpose::input",
503 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
504 event_time.platform_time_ms,
505 event_time.animation_time_nanos,
506 );
507 result
508 }
509
510 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
516 let event_time = self.realtime_pointer_event_time(None);
517 let _event_handler = enter_event_handler_scope();
518 let app_context = Rc::clone(&self.app_context);
519 let result = app_context.enter(|| {
520 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
521 .unwrap_or(false)
522 });
523 if result {
524 self.mark_dirty();
525 }
526 log::trace!(
527 target: "cranpose::input",
528 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
529 );
530 result
531 }
532
533 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
534 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
535 return false;
536 }
537
538 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
539 if hits.is_empty() {
540 return false;
541 }
542
543 let pos = Point {
544 x: self.cursor.0,
545 y: self.cursor.1,
546 };
547 let event = self
548 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
549 .with_buttons(self.buttons_pressed)
550 .with_zoom_delta(zoom_factor)
551 .with_source(self.pointer_source);
552
553 let capture_paths = hits
554 .iter()
555 .map(|hit| hit.capture_path())
556 .collect::<Vec<_>>();
557 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
558 .into_iter()
559 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
560 .collect::<Vec<_>>();
561
562 self.dispatch_targets(targets, event.clone(), true);
563
564 event.is_consumed()
565 }
566
567 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
592 if wheel.is_zoom() {
593 let zoom_factor = wheel.zoom_factor();
594 log::trace!(
595 target: "cranpose::input",
596 "wheel zoom factor={zoom_factor:.4}"
597 );
598 return self.pointer_zoomed(zoom_factor);
599 }
600
601 let rotary =
602 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
603 if self.rotary_scrolled(rotary) {
604 return true;
605 }
606
607 let delta = wheel.scroll_delta();
608 log::trace!(
609 target: "cranpose::input",
610 "wheel delta ({:.2},{:.2}) alt={}",
611 delta.x,
612 delta.y,
613 wheel.modifiers.alt
614 );
615 self.pointer_scrolled(delta.x, delta.y)
616 }
617
618 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
626 let event_time = self.realtime_pointer_event_time(None);
627 let _event_handler = enter_event_handler_scope();
628 let app_context = Rc::clone(&self.app_context);
629 let result = app_context.enter(|| {
630 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
631 .unwrap_or(false)
632 });
633 if result {
634 self.mark_dirty();
635 }
636 log::trace!(
637 target: "cranpose::input",
638 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
639 );
640 result
641 }
642
643 fn pointer_scrolled_inner(
644 &mut self,
645 delta_x: f32,
646 delta_y: f32,
647 event_time: PointerEventTime,
648 ) -> bool {
649 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
650 return false;
651 }
652
653 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
654 if hits.is_empty() {
655 return false;
656 }
657
658 let event = self
659 .pointer_event(
660 PointerEventKind::Scroll,
661 Point {
662 x: self.cursor.0,
663 y: self.cursor.1,
664 },
665 Point {
666 x: self.cursor.0,
667 y: self.cursor.1,
668 },
669 event_time,
670 )
671 .with_buttons(self.buttons_pressed)
672 .with_scroll_delta(Point {
673 x: delta_x,
674 y: delta_y,
675 })
676 .with_source(self.pointer_source);
677
678 let capture_paths = hits
679 .iter()
680 .map(|hit| hit.capture_path())
681 .collect::<Vec<_>>();
682 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
683 .into_iter()
684 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
685 .collect::<Vec<_>>();
686
687 self.dispatch_targets(targets, event.clone(), true);
688
689 event.is_consumed()
690 }
691
692 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
703 where
704 F: Fn(RotaryScrollEvent) -> bool + 'static,
705 {
706 self.on_rotary_scroll = Some(Rc::new(handler));
707 }
708
709 pub fn clear_on_rotary_scroll(&mut self) {
711 self.on_rotary_scroll = None;
712 }
713
714 pub fn rotary_scroll_factor(&self) -> f32 {
717 self.rotary_scroll_factor
718 }
719
720 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
729 if factor.is_finite() && factor > 0.0 {
730 self.rotary_scroll_factor = factor;
731 }
732 }
733
734 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
740 let factor = self.rotary_scroll_factor;
741 self.rotary_scrolled(RotaryScrollEvent::from_detents(
742 detents,
743 factor,
744 factor,
745 uptime_millis,
746 ))
747 }
748
749 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
770 let _event_handler = enter_event_handler_scope();
771 let app_context = Rc::clone(&self.app_context);
772 let result = app_context.enter(|| {
773 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
774 });
775 if result {
776 self.mark_dirty();
777 }
778 log::trace!(
779 target: "cranpose::input",
780 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
781 event.vertical_scroll_pixels,
782 event.horizontal_scroll_pixels,
783 event.uptime_millis,
784 );
785 result
786 }
787
788 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
789 if rotary.is_empty() {
790 return false;
791 }
792
793 let bubble_order = self.rotary_dispatch_order();
794 let position = Point {
795 x: self.cursor.0,
796 y: self.cursor.1,
797 };
798
799 if !bubble_order.is_empty() {
800 let capture_targets = bubble_order
801 .iter()
802 .rev()
803 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
804 .collect::<Vec<_>>();
805 let mut capture_event =
806 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
807 capture_event.modifiers = self.modifiers;
808 self.dispatch_targets(capture_targets, capture_event.clone(), true);
809 if capture_event.is_consumed() {
810 return true;
811 }
812
813 let bubble_targets = bubble_order
814 .iter()
815 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
816 .collect::<Vec<_>>();
817 let mut bubble_event =
818 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
819 bubble_event.modifiers = self.modifiers;
820 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
821 if bubble_event.is_consumed() {
822 return true;
823 }
824 }
825
826 if let Some(handler) = self.on_rotary_scroll.clone() {
827 return handler(rotary);
828 }
829
830 false
831 }
832
833 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
834 if let Some(focused) = cranpose_ui::active_focus_target()
835 && let Some(target) = self.renderer.scene().find_target(focused)
836 {
837 let path = target.capture_path();
838 if !path.is_empty() {
839 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
840 }
841 }
842
843 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
844 if hits.is_empty() {
845 return Vec::new();
846 }
847 let capture_paths = hits
848 .iter()
849 .map(|hit| hit.capture_path())
850 .collect::<Vec<_>>();
851 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
852 }
853
854 pub fn cancel_gesture(&mut self) {
860 let event_time = self.realtime_pointer_event_time(None);
861 let _event_handler = enter_event_handler_scope();
862 let app_context = Rc::clone(&self.app_context);
863 let _ = app_context.enter(|| {
864 run_in_mutable_snapshot(|| {
865 self.cancel_gesture_inner(event_time);
866 })
867 });
868 }
869
870 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
871 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
872
873 self.hit_path_tracker.clear();
874 self.buttons_pressed = PointerButtons::NONE;
875
876 if !targets.is_empty() {
877 let event = self
878 .pointer_event(
879 PointerEventKind::Cancel,
880 Point {
881 x: self.cursor.0,
882 y: self.cursor.1,
883 },
884 Point {
885 x: self.cursor.0,
886 y: self.cursor.1,
887 },
888 event_time,
889 )
890 .with_source(self.pointer_source);
891
892 self.dispatch_targets(targets, event, false);
893 }
894
895 let pos = Point {
896 x: self.cursor.0,
897 y: self.cursor.1,
898 };
899 let hovered_nodes = self.hovered_nodes.clone();
900 for node_id in hovered_nodes {
901 if let Some(target) = self.renderer.scene().find_target(node_id) {
902 let exit_event = self
903 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
904 .with_source(self.pointer_source);
905 self.dispatch_targets(std::iter::once(target), exit_event, false);
906 }
907 }
908 self.hovered_nodes.clear();
909 }
910
911 pub fn set_platform_text_input(
918 &mut self,
919 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
920 ) {
921 let app_context = Rc::clone(&self.app_context);
922 app_context
923 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
924 }
925
926 pub fn notify_app_paused(&mut self) {
933 let app_context = Rc::clone(&self.app_context);
934 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
935 }
936
937 pub fn notify_app_resumed(&mut self) -> bool {
945 let app_context = Rc::clone(&self.app_context);
946 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
947 }
948
949 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
956 let _event_handler = enter_event_handler_scope();
957 let app_context = Rc::clone(&self.app_context);
958 app_context.enter(|| self.on_key_event_inner(event))
959 }
960
961 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
962 use KeyEventType::KeyDown;
963
964 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
965 #[cfg(all(
966 feature = "clipboard-native",
967 not(target_arch = "wasm32"),
968 not(target_os = "android"),
969 not(target_os = "ios")
970 ))]
971 {
972 match event.key_code {
973 KeyCode::C => {
974 if let Some(text) = self.on_copy_inner() {
975 cranpose_ui::clipboard_session::clipboard_write_text(&text);
976 return true;
977 }
978 }
979 KeyCode::X => {
980 if let Some(text) = self.on_cut_inner() {
981 cranpose_ui::clipboard_session::clipboard_write_text(&text);
982 self.mark_dirty();
983 self.request_layout_pass();
984 return true;
985 }
986 }
987 KeyCode::V => {
988 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
989 && self.on_paste_inner(&text)
990 {
991 return true;
992 }
993 }
994 _ => {}
995 }
996 }
997 }
998
999 if !cranpose_ui::text_field_focus::has_focused_field() {
1000 return false;
1001 }
1002
1003 let handled =
1004 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1005 .unwrap_or(false);
1006
1007 if handled {
1008 self.mark_dirty();
1009 self.request_layout_pass();
1010 }
1011
1012 handled
1013 }
1014
1015 pub fn on_paste(&mut self, text: &str) -> bool {
1019 let _event_handler = enter_event_handler_scope();
1020 let app_context = Rc::clone(&self.app_context);
1021 app_context.enter(|| self.on_paste_inner(text))
1022 }
1023
1024 fn on_paste_inner(&mut self, text: &str) -> bool {
1025 let handled =
1026 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1027 .unwrap_or(false);
1028
1029 if handled {
1030 self.mark_dirty();
1031 self.request_layout_pass();
1032 }
1033
1034 handled
1035 }
1036
1037 pub fn on_copy(&mut self) -> Option<String> {
1041 let app_context = Rc::clone(&self.app_context);
1042 app_context.enter(|| self.on_copy_inner())
1043 }
1044
1045 fn on_copy_inner(&mut self) -> Option<String> {
1046 cranpose_ui::text_field_focus::dispatch_copy()
1047 }
1048
1049 pub fn on_cut(&mut self) -> Option<String> {
1053 let _event_handler = enter_event_handler_scope();
1054 let app_context = Rc::clone(&self.app_context);
1055 app_context.enter(|| self.on_cut_inner())
1056 }
1057
1058 fn on_cut_inner(&mut self) -> Option<String> {
1059 let text =
1060 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1061
1062 if text.is_some() {
1063 self.mark_dirty();
1064 self.request_layout_pass();
1065 }
1066
1067 text
1068 }
1069
1070 #[cfg(all(
1074 feature = "clipboard-native",
1075 target_os = "linux",
1076 not(target_arch = "wasm32")
1077 ))]
1078 pub fn set_primary_selection(&mut self, text: &str) {
1079 use arboard::{LinuxClipboardKind, SetExtLinux};
1080 if let Some(ref mut clipboard) = self.clipboard {
1081 let result = clipboard
1082 .set()
1083 .clipboard(LinuxClipboardKind::Primary)
1084 .text(text.to_string());
1085 if let Err(e) = result {
1086 log::debug!("Primary selection set failed: {:?}", e);
1087 }
1088 }
1089 }
1090
1091 #[cfg(not(all(
1092 feature = "clipboard-native",
1093 target_os = "linux",
1094 not(target_arch = "wasm32")
1095 )))]
1096 pub fn set_primary_selection(&mut self, _text: &str) {}
1097
1098 #[cfg(all(
1101 feature = "clipboard-native",
1102 target_os = "linux",
1103 not(target_arch = "wasm32")
1104 ))]
1105 pub fn get_primary_selection(&mut self) -> Option<String> {
1106 use arboard::{GetExtLinux, LinuxClipboardKind};
1107 if let Some(ref mut clipboard) = self.clipboard {
1108 clipboard
1109 .get()
1110 .clipboard(LinuxClipboardKind::Primary)
1111 .text()
1112 .ok()
1113 } else {
1114 None
1115 }
1116 }
1117
1118 #[cfg(not(all(
1119 feature = "clipboard-native",
1120 target_os = "linux",
1121 not(target_arch = "wasm32")
1122 )))]
1123 pub fn get_primary_selection(&mut self) -> Option<String> {
1124 None
1125 }
1126
1127 pub fn sync_selection_to_primary(&mut self) {
1130 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1131 {
1132 if let Some(text) = self.on_copy() {
1133 self.set_primary_selection(&text);
1134 }
1135 }
1136 }
1137
1138 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1146 let _event_handler = enter_event_handler_scope();
1147 let app_context = Rc::clone(&self.app_context);
1148 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1149 }
1150
1151 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1152 let handled = run_in_mutable_snapshot(|| {
1153 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1154 })
1155 .unwrap_or(false);
1156
1157 if handled {
1158 self.mark_dirty();
1159 self.request_layout_pass();
1160 }
1161
1162 handled
1163 }
1164
1165 pub fn on_ime_finish_composing(&mut self) -> bool {
1169 let _event_handler = enter_event_handler_scope();
1170 let app_context = Rc::clone(&self.app_context);
1171 app_context.enter(|| self.on_ime_finish_composing_inner())
1172 }
1173
1174 fn on_ime_finish_composing_inner(&mut self) -> bool {
1175 let handled =
1176 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1177 .unwrap_or(false);
1178
1179 if handled {
1180 self.mark_dirty();
1181 self.request_layout_pass();
1182 }
1183
1184 handled
1185 }
1186
1187 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1191 let _event_handler = enter_event_handler_scope();
1192 let app_context = Rc::clone(&self.app_context);
1193 app_context.enter(|| {
1194 let handled = run_in_mutable_snapshot(|| {
1195 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1196 start_bytes,
1197 end_bytes,
1198 )
1199 })
1200 .unwrap_or(false);
1201
1202 if handled {
1203 self.mark_dirty();
1204 self.request_layout_pass();
1205 }
1206
1207 handled
1208 })
1209 }
1210
1211 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1216 let _event_handler = enter_event_handler_scope();
1217 let app_context = Rc::clone(&self.app_context);
1218 app_context.enter(|| {
1219 let handled = run_in_mutable_snapshot(|| {
1220 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1221 })
1222 .unwrap_or(false);
1223
1224 if handled {
1225 self.mark_dirty();
1226 }
1227
1228 handled
1229 })
1230 }
1231
1232 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1236 let app_context = Rc::clone(&self.app_context);
1237 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1238 }
1239
1240 pub fn ime_caret_geometry(
1244 &mut self,
1245 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1246 let app_context = Rc::clone(&self.app_context);
1247 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1248 }
1249
1250 pub fn clear_text_field_focus(&mut self) {
1253 let _event_handler = enter_event_handler_scope();
1254 let app_context = Rc::clone(&self.app_context);
1255 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1256 self.mark_dirty();
1257 self.request_layout_pass();
1258 }
1259
1260 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1263 let _event_handler = enter_event_handler_scope();
1264 let app_context = Rc::clone(&self.app_context);
1265 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1266 }
1267
1268 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1269 let handled = run_in_mutable_snapshot(|| {
1270 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1271 })
1272 .unwrap_or(false);
1273
1274 if handled {
1275 self.mark_dirty();
1276 self.request_layout_pass();
1277 }
1278
1279 handled
1280 }
1281}