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 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 }
19
20 fn resolve_gesture_targets(
21 &self,
22 pointer: PointerId,
23 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
24 self.resolve_hit_path(pointer)
25 }
26
27 fn resolve_hit_path(
34 &self,
35 pointer: PointerId,
36 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
37 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
38 return Vec::new();
39 };
40
41 let scene = self.renderer.scene();
42 let targets: Vec<_> = node_ids
43 .iter()
44 .filter_map(|&id| scene.find_target(id))
45 .collect();
46 log::trace!(
47 target: "cranpose::input",
48 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
49 targets.len()
50 );
51 targets
52 }
53
54 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
55 where
56 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
57 {
58 let mut applier = self.composition.applier_mut();
59 for target in targets {
60 let node_id = target.node_id();
61 target.dispatch_with_applier(&mut applier, event.clone());
62 log::trace!(
63 target: "cranpose::input",
64 "dispatch {:?} node={} consumed={} stop_on_consume={}",
65 event.kind,
66 node_id,
67 event.is_consumed(),
68 stop_on_consume,
69 );
70 if stop_on_consume && event.is_consumed() {
71 break;
72 }
73 }
74 event.finish_post_dispatch();
75 }
76
77 pub fn set_pointer_source(&mut self, source: PointerSource) {
83 self.pointer_source = source;
84 }
85
86 pub fn pointer_source(&self) -> PointerSource {
88 self.pointer_source
89 }
90
91 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
92 self.set_cursor_at_time(x, y, None)
93 }
94
95 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
102 let event_time = self.realtime_pointer_event_time(time_ms);
103 self.set_cursor_at_event_time(x, y, event_time)
104 }
105
106 pub fn set_cursor_at_event_time(
108 &mut self,
109 x: f32,
110 y: f32,
111 event_time: PointerEventTime,
112 ) -> bool {
113 let _event_handler = enter_event_handler_scope();
114 let app_context = Rc::clone(&self.app_context);
115 let result = app_context.enter(|| {
116 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
117 });
118 if result {
119 self.mark_dirty();
120 }
121 log::trace!(
122 target: "cranpose::input",
123 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
124 event_time.platform_time_ms,
125 event_time.animation_time_nanos,
126 );
127 result
128 }
129
130 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
131 self.cursor = (x, y);
132
133 if self.buttons_pressed != PointerButtons::NONE {
137 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
138 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
139 if !targets.is_empty() {
140 let event = self
141 .pointer_event(
142 PointerEventKind::Move,
143 Point { x, y },
144 Point { x, y },
145 event_time,
146 )
147 .with_buttons(self.buttons_pressed)
148 .with_source(self.pointer_source);
149 self.dispatch_targets(targets, event, false);
150 return true;
151 }
152
153 return false;
154 }
155
156 return false;
159 }
160
161 let hits = self.renderer.scene().hit_test(x, y);
164 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
165
166 let pos = Point { x, y };
168 let previously_hovered = self.hovered_nodes.clone();
169 for old_id in previously_hovered {
170 if !new_ids.contains(&old_id) {
171 if let Some(target) = self.renderer.scene().find_target(old_id) {
172 let exit_event = self
173 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
174 .with_buttons(self.buttons_pressed)
175 .with_source(self.pointer_source);
176 self.dispatch_targets(std::iter::once(target), exit_event, false);
177 }
178 }
179 }
180
181 for hit in &hits {
183 if !self.hovered_nodes.contains(&hit.node_id()) {
184 let enter_event = self
185 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
186 .with_buttons(self.buttons_pressed)
187 .with_source(self.pointer_source);
188 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
189 }
190 }
191
192 self.hovered_nodes = new_ids;
193
194 if !hits.is_empty() {
195 let event = self
196 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
197 .with_buttons(self.buttons_pressed)
198 .with_source(self.pointer_source);
199 self.dispatch_targets(hits, event, true);
200 true
201 } else {
202 false
203 }
204 }
205
206 pub fn pointer_pressed(&mut self) -> bool {
207 self.pointer_pressed_at_time(None)
208 }
209
210 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
213 let event_time = self.realtime_pointer_event_time(time_ms);
214 self.pointer_pressed_at_event_time(event_time)
215 }
216
217 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
219 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
223 return true;
224 }
225 let _event_handler = enter_event_handler_scope();
226 let app_context = Rc::clone(&self.app_context);
227 let result = app_context.enter(|| {
228 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
229 });
230 if result {
231 self.mark_dirty();
232 }
233 log::trace!(
234 target: "cranpose::input",
235 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
236 event_time.platform_time_ms,
237 event_time.animation_time_nanos,
238 );
239 result
240 }
241
242 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
243 self.buttons_pressed.insert(PointerButton::Primary);
245
246 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
254 if hits.is_empty() {
255 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
256 false
257 } else {
258 let event = self
259 .pointer_event(
260 PointerEventKind::Down,
261 Point {
262 x: self.cursor.0,
263 y: self.cursor.1,
264 },
265 Point {
266 x: self.cursor.0,
267 y: self.cursor.1,
268 },
269 event_time,
270 )
271 .with_buttons(self.buttons_pressed)
272 .with_source(self.pointer_source);
273
274 let mut delivered_capture_paths = Vec::new();
275 let mut applier = self.composition.applier_mut();
276 for hit in hits {
277 let node_id = hit.node_id();
278 delivered_capture_paths.push(hit.capture_path());
279 hit.dispatch_with_applier(&mut applier, event.clone());
280 log::trace!(
281 target: "cranpose::input",
282 "dispatch {:?} node={} consumed={} stop_on_consume=true",
283 event.kind,
284 node_id,
285 event.is_consumed(),
286 );
287 if event.is_consumed() {
288 break;
289 }
290 }
291
292 self.hit_path_tracker
293 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
294 log::trace!(
295 target: "cranpose::input",
296 "pointer_pressed_inner cached_hit_path={:?}",
297 self.hit_path_tracker.get_path(PointerId::PRIMARY),
298 );
299
300 true
301 }
302 }
303
304 pub fn pointer_released(&mut self) -> bool {
305 self.pointer_released_at_time(None)
306 }
307
308 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
321 self.pointer_released_at_position_time(x, y, None)
322 }
323
324 pub fn pointer_released_at_position_time(
328 &mut self,
329 x: f32,
330 y: f32,
331 time_ms: Option<i64>,
332 ) -> bool {
333 let event_time = self.realtime_pointer_event_time(time_ms);
334 self.pointer_released_at_position_event_time(x, y, event_time)
335 }
336
337 pub fn pointer_released_at_position_event_time(
339 &mut self,
340 x: f32,
341 y: f32,
342 event_time: PointerEventTime,
343 ) -> bool {
344 let _event_handler = enter_event_handler_scope();
345 let app_context = Rc::clone(&self.app_context);
346 let result = app_context.enter(|| {
347 run_in_mutable_snapshot(|| {
348 self.cursor = (x, y);
349 self.pointer_released_inner(event_time)
350 })
351 .unwrap_or(false)
352 });
353 if result {
354 self.mark_dirty();
355 }
356 log::trace!(
357 target: "cranpose::input",
358 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
359 event_time.platform_time_ms,
360 event_time.animation_time_nanos,
361 );
362 result
363 }
364
365 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
368 let event_time = self.realtime_pointer_event_time(time_ms);
369 self.pointer_released_at_event_time(event_time)
370 }
371
372 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
374 let _event_handler = enter_event_handler_scope();
375 let app_context = Rc::clone(&self.app_context);
376 let result = app_context.enter(|| {
377 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
378 });
379 if result {
380 self.mark_dirty();
381 }
382 log::trace!(
383 target: "cranpose::input",
384 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
385 event_time.platform_time_ms,
386 event_time.animation_time_nanos,
387 );
388 result
389 }
390
391 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
392 self.buttons_pressed.remove(PointerButton::Primary);
395 let corrected_buttons = self.buttons_pressed;
396 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
397
398 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
400
401 if !targets.is_empty() {
402 let event = self
403 .pointer_event(
404 PointerEventKind::Up,
405 Point {
406 x: self.cursor.0,
407 y: self.cursor.1,
408 },
409 Point {
410 x: self.cursor.0,
411 y: self.cursor.1,
412 },
413 event_time,
414 )
415 .with_buttons(corrected_buttons)
416 .with_source(self.pointer_source);
417
418 self.dispatch_targets(targets, event, false);
419 true
420 } else {
421 false
422 }
423 }
424
425 pub fn secondary_pointer_pressed(
434 &mut self,
435 pointer_id: u64,
436 x: f32,
437 y: f32,
438 time_ms: Option<i64>,
439 ) -> bool {
440 let event_time = self.realtime_pointer_event_time(time_ms);
441 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
442 }
443
444 pub fn secondary_pointer_moved(
446 &mut self,
447 pointer_id: u64,
448 x: f32,
449 y: f32,
450 time_ms: Option<i64>,
451 ) -> bool {
452 let event_time = self.realtime_pointer_event_time(time_ms);
453 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
454 }
455
456 pub fn secondary_pointer_released(
458 &mut self,
459 pointer_id: u64,
460 x: f32,
461 y: f32,
462 time_ms: Option<i64>,
463 ) -> bool {
464 let event_time = self.realtime_pointer_event_time(time_ms);
465 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
466 }
467
468 fn dispatch_secondary_pointer(
469 &mut self,
470 kind: PointerEventKind,
471 pointer_id: u64,
472 x: f32,
473 y: f32,
474 event_time: PointerEventTime,
475 ) -> bool {
476 if pointer_id == 0 {
477 log::warn!(
478 target: "cranpose::input",
479 "secondary pointer dispatch called with the primary pointer id"
480 );
481 return false;
482 }
483
484 let _event_handler = enter_event_handler_scope();
485 let app_context = Rc::clone(&self.app_context);
486 let result = app_context.enter(|| {
487 run_in_mutable_snapshot(|| {
488 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
489 return false;
490 }
491 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
492 if targets.is_empty() {
493 return false;
494 }
495 let pos = Point { x, y };
496 let event = self
497 .pointer_event(kind, pos, pos, event_time)
498 .with_buttons(self.buttons_pressed)
499 .with_id(pointer_id)
500 .with_source(self.pointer_source);
501 self.dispatch_targets(targets, event, false);
502 true
503 })
504 .unwrap_or(false)
505 });
506 if result {
507 self.mark_dirty();
508 }
509 log::trace!(
510 target: "cranpose::input",
511 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
512 event_time.platform_time_ms,
513 event_time.animation_time_nanos,
514 );
515 result
516 }
517
518 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
524 let event_time = self.realtime_pointer_event_time(None);
525 let _event_handler = enter_event_handler_scope();
526 let app_context = Rc::clone(&self.app_context);
527 let result = app_context.enter(|| {
528 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
529 .unwrap_or(false)
530 });
531 if result {
532 self.mark_dirty();
533 }
534 log::trace!(
535 target: "cranpose::input",
536 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
537 );
538 result
539 }
540
541 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
542 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
543 return false;
544 }
545
546 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
547 if hits.is_empty() {
548 return false;
549 }
550
551 let pos = Point {
552 x: self.cursor.0,
553 y: self.cursor.1,
554 };
555 let event = self
556 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
557 .with_buttons(self.buttons_pressed)
558 .with_zoom_delta(zoom_factor)
559 .with_source(self.pointer_source);
560
561 let capture_paths = hits
562 .iter()
563 .map(|hit| hit.capture_path())
564 .collect::<Vec<_>>();
565 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
566 .into_iter()
567 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
568 .collect::<Vec<_>>();
569
570 self.dispatch_targets(targets, event.clone(), true);
571
572 event.is_consumed()
573 }
574
575 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
600 if wheel.is_zoom() {
601 let zoom_factor = wheel.zoom_factor();
602 log::trace!(
603 target: "cranpose::input",
604 "wheel zoom factor={zoom_factor:.4}"
605 );
606 return self.pointer_zoomed(zoom_factor);
607 }
608
609 let rotary =
610 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
611 if self.rotary_scrolled(rotary) {
612 return true;
613 }
614
615 let delta = wheel.scroll_delta();
616 log::trace!(
617 target: "cranpose::input",
618 "wheel delta ({:.2},{:.2}) alt={}",
619 delta.x,
620 delta.y,
621 wheel.modifiers.alt
622 );
623 self.pointer_scrolled(delta.x, delta.y)
624 }
625
626 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
634 let event_time = self.realtime_pointer_event_time(None);
635 let _event_handler = enter_event_handler_scope();
636 let app_context = Rc::clone(&self.app_context);
637 let result = app_context.enter(|| {
638 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
639 .unwrap_or(false)
640 });
641 if result {
642 self.mark_dirty();
643 }
644 log::trace!(
645 target: "cranpose::input",
646 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
647 );
648 result
649 }
650
651 fn pointer_scrolled_inner(
652 &mut self,
653 delta_x: f32,
654 delta_y: f32,
655 event_time: PointerEventTime,
656 ) -> bool {
657 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
658 return false;
659 }
660
661 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
662 if hits.is_empty() {
663 return false;
664 }
665
666 let event = self
667 .pointer_event(
668 PointerEventKind::Scroll,
669 Point {
670 x: self.cursor.0,
671 y: self.cursor.1,
672 },
673 Point {
674 x: self.cursor.0,
675 y: self.cursor.1,
676 },
677 event_time,
678 )
679 .with_buttons(self.buttons_pressed)
680 .with_scroll_delta(Point {
681 x: delta_x,
682 y: delta_y,
683 })
684 .with_source(self.pointer_source);
685
686 let capture_paths = hits
687 .iter()
688 .map(|hit| hit.capture_path())
689 .collect::<Vec<_>>();
690 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
691 .into_iter()
692 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
693 .collect::<Vec<_>>();
694
695 self.dispatch_targets(targets, event.clone(), true);
696
697 event.is_consumed()
698 }
699
700 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
711 where
712 F: Fn(RotaryScrollEvent) -> bool + 'static,
713 {
714 self.on_rotary_scroll = Some(Rc::new(handler));
715 }
716
717 pub fn clear_on_rotary_scroll(&mut self) {
719 self.on_rotary_scroll = None;
720 }
721
722 pub fn rotary_scroll_factor(&self) -> f32 {
725 self.rotary_scroll_factor
726 }
727
728 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
737 if factor.is_finite() && factor > 0.0 {
738 self.rotary_scroll_factor = factor;
739 }
740 }
741
742 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
748 let factor = self.rotary_scroll_factor;
749 self.rotary_scrolled(RotaryScrollEvent::from_detents(
750 detents,
751 factor,
752 factor,
753 uptime_millis,
754 ))
755 }
756
757 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
778 let _event_handler = enter_event_handler_scope();
779 let app_context = Rc::clone(&self.app_context);
780 let result = app_context.enter(|| {
781 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
782 });
783 if result {
784 self.mark_dirty();
785 }
786 log::trace!(
787 target: "cranpose::input",
788 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
789 event.vertical_scroll_pixels,
790 event.horizontal_scroll_pixels,
791 event.uptime_millis,
792 );
793 result
794 }
795
796 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
797 if rotary.is_empty() {
798 return false;
799 }
800
801 let bubble_order = self.rotary_dispatch_order();
804 let position = Point {
805 x: self.cursor.0,
806 y: self.cursor.1,
807 };
808
809 if !bubble_order.is_empty() {
810 let capture_targets = bubble_order
812 .iter()
813 .rev()
814 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
815 .collect::<Vec<_>>();
816 let capture_event =
817 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
818 self.dispatch_targets(capture_targets, capture_event.clone(), true);
819 if capture_event.is_consumed() {
820 return true;
821 }
822
823 let bubble_targets = bubble_order
825 .iter()
826 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
827 .collect::<Vec<_>>();
828 let bubble_event =
829 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
830 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
831 if bubble_event.is_consumed() {
832 return true;
833 }
834 }
835
836 if let Some(handler) = self.on_rotary_scroll.clone() {
838 return handler(rotary);
839 }
840
841 false
842 }
843
844 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
850 if let Some(focused) = cranpose_ui::active_focus_target() {
851 if let Some(target) = self.renderer.scene().find_target(focused) {
852 let path = target.capture_path();
853 if !path.is_empty() {
854 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
855 }
856 }
857 }
858
859 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
860 if hits.is_empty() {
861 return Vec::new();
862 }
863 let capture_paths = hits
864 .iter()
865 .map(|hit| hit.capture_path())
866 .collect::<Vec<_>>();
867 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
868 }
869
870 pub fn cancel_gesture(&mut self) {
876 let event_time = self.realtime_pointer_event_time(None);
877 let _event_handler = enter_event_handler_scope();
878 let app_context = Rc::clone(&self.app_context);
879 let _ = app_context.enter(|| {
880 run_in_mutable_snapshot(|| {
881 self.cancel_gesture_inner(event_time);
882 })
883 });
884 }
885
886 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
887 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
888
889 self.hit_path_tracker.clear();
891 self.buttons_pressed = PointerButtons::NONE;
892
893 if !targets.is_empty() {
894 let event = self
895 .pointer_event(
896 PointerEventKind::Cancel,
897 Point {
898 x: self.cursor.0,
899 y: self.cursor.1,
900 },
901 Point {
902 x: self.cursor.0,
903 y: self.cursor.1,
904 },
905 event_time,
906 )
907 .with_source(self.pointer_source);
908
909 self.dispatch_targets(targets, event, false);
910 }
911
912 let pos = Point {
914 x: self.cursor.0,
915 y: self.cursor.1,
916 };
917 let hovered_nodes = self.hovered_nodes.clone();
918 for node_id in hovered_nodes {
919 if let Some(target) = self.renderer.scene().find_target(node_id) {
920 let exit_event = self
921 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
922 .with_source(self.pointer_source);
923 self.dispatch_targets(std::iter::once(target), exit_event, false);
924 }
925 }
926 self.hovered_nodes.clear();
927 }
928
929 pub fn set_platform_text_input(
936 &mut self,
937 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
938 ) {
939 let app_context = Rc::clone(&self.app_context);
940 app_context
941 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
942 }
943
944 pub fn clear_platform_text_input(&mut self) {
946 let app_context = Rc::clone(&self.app_context);
947 app_context.enter(cranpose_ui::text_input_session::clear_platform_text_input_handler);
948 }
949
950 pub fn notify_app_paused(&mut self) {
957 let app_context = Rc::clone(&self.app_context);
958 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
959 }
960
961 pub fn notify_app_resumed(&mut self) -> bool {
969 let app_context = Rc::clone(&self.app_context);
970 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
971 }
972
973 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
980 let _event_handler = enter_event_handler_scope();
981 let app_context = Rc::clone(&self.app_context);
982 app_context.enter(|| self.on_key_event_inner(event))
983 }
984
985 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
987 use KeyEventType::KeyDown;
988
989 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
991 #[cfg(all(
992 feature = "clipboard-native",
993 not(target_arch = "wasm32"),
994 not(target_os = "android"),
995 not(target_os = "ios")
996 ))]
997 {
998 match event.key_code {
999 KeyCode::C => {
1001 if let Some(text) = self.on_copy_inner() {
1002 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1003 return true;
1004 }
1005 }
1006 KeyCode::X => {
1008 if let Some(text) = self.on_cut_inner() {
1009 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1010 self.mark_dirty();
1011 self.request_layout_pass();
1012 return true;
1013 }
1014 }
1015 KeyCode::V => {
1017 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text() {
1018 if self.on_paste_inner(&text) {
1019 return true;
1020 }
1021 }
1022 }
1023 _ => {}
1024 }
1025 }
1026 }
1027
1028 if !cranpose_ui::text_field_focus::has_focused_field() {
1030 return false;
1031 }
1032
1033 let handled = run_in_mutable_snapshot(|| {
1037 cranpose_ui::text_field_focus::dispatch_key_event(event)
1040 })
1041 .unwrap_or(false);
1042
1043 if handled {
1044 self.mark_dirty();
1046 self.request_layout_pass();
1047 }
1048
1049 handled
1050 }
1051
1052 pub fn on_paste(&mut self, text: &str) -> bool {
1056 let _event_handler = enter_event_handler_scope();
1057 let app_context = Rc::clone(&self.app_context);
1058 app_context.enter(|| self.on_paste_inner(text))
1059 }
1060
1061 fn on_paste_inner(&mut self, text: &str) -> bool {
1062 let handled =
1066 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1067 .unwrap_or(false);
1068
1069 if handled {
1070 self.mark_dirty();
1071 self.request_layout_pass();
1072 }
1073
1074 handled
1075 }
1076
1077 pub fn on_copy(&mut self) -> Option<String> {
1081 let app_context = Rc::clone(&self.app_context);
1082 app_context.enter(|| self.on_copy_inner())
1083 }
1084
1085 fn on_copy_inner(&mut self) -> Option<String> {
1086 cranpose_ui::text_field_focus::dispatch_copy()
1088 }
1089
1090 pub fn on_cut(&mut self) -> Option<String> {
1094 let _event_handler = enter_event_handler_scope();
1095 let app_context = Rc::clone(&self.app_context);
1096 app_context.enter(|| self.on_cut_inner())
1097 }
1098
1099 fn on_cut_inner(&mut self) -> Option<String> {
1100 let text =
1101 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1102
1103 if text.is_some() {
1104 self.mark_dirty();
1105 self.request_layout_pass();
1106 }
1107
1108 text
1109 }
1110
1111 #[cfg(all(
1115 feature = "clipboard-native",
1116 target_os = "linux",
1117 not(target_arch = "wasm32")
1118 ))]
1119 pub fn set_primary_selection(&mut self, text: &str) {
1120 use arboard::{LinuxClipboardKind, SetExtLinux};
1121 if let Some(ref mut clipboard) = self.clipboard {
1122 let result = clipboard
1123 .set()
1124 .clipboard(LinuxClipboardKind::Primary)
1125 .text(text.to_string());
1126 if let Err(e) = result {
1127 log::debug!("Primary selection set failed: {:?}", e);
1129 }
1130 }
1131 }
1132
1133 #[cfg(not(all(
1134 feature = "clipboard-native",
1135 target_os = "linux",
1136 not(target_arch = "wasm32")
1137 )))]
1138 pub fn set_primary_selection(&mut self, _text: &str) {}
1139
1140 #[cfg(all(
1143 feature = "clipboard-native",
1144 target_os = "linux",
1145 not(target_arch = "wasm32")
1146 ))]
1147 pub fn get_primary_selection(&mut self) -> Option<String> {
1148 use arboard::{GetExtLinux, LinuxClipboardKind};
1149 if let Some(ref mut clipboard) = self.clipboard {
1150 clipboard
1151 .get()
1152 .clipboard(LinuxClipboardKind::Primary)
1153 .text()
1154 .ok()
1155 } else {
1156 None
1157 }
1158 }
1159
1160 #[cfg(not(all(
1161 feature = "clipboard-native",
1162 target_os = "linux",
1163 not(target_arch = "wasm32")
1164 )))]
1165 pub fn get_primary_selection(&mut self) -> Option<String> {
1166 None
1167 }
1168
1169 pub fn sync_selection_to_primary(&mut self) {
1172 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1173 {
1174 if let Some(text) = self.on_copy() {
1175 self.set_primary_selection(&text);
1176 }
1177 }
1178 }
1179
1180 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1188 let _event_handler = enter_event_handler_scope();
1189 let app_context = Rc::clone(&self.app_context);
1190 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1191 }
1192
1193 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1194 let handled = run_in_mutable_snapshot(|| {
1196 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1197 })
1198 .unwrap_or(false);
1199
1200 if handled {
1201 self.mark_dirty();
1202 self.request_layout_pass();
1204 }
1205
1206 handled
1207 }
1208
1209 pub fn on_ime_finish_composing(&mut self) -> bool {
1213 let _event_handler = enter_event_handler_scope();
1214 let app_context = Rc::clone(&self.app_context);
1215 app_context.enter(|| self.on_ime_finish_composing_inner())
1216 }
1217
1218 fn on_ime_finish_composing_inner(&mut self) -> bool {
1219 let handled =
1220 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1221 .unwrap_or(false);
1222
1223 if handled {
1224 self.mark_dirty();
1225 self.request_layout_pass();
1226 }
1227
1228 handled
1229 }
1230
1231 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1235 let _event_handler = enter_event_handler_scope();
1236 let app_context = Rc::clone(&self.app_context);
1237 app_context.enter(|| {
1238 let handled = run_in_mutable_snapshot(|| {
1239 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1240 start_bytes,
1241 end_bytes,
1242 )
1243 })
1244 .unwrap_or(false);
1245
1246 if handled {
1247 self.mark_dirty();
1248 self.request_layout_pass();
1249 }
1250
1251 handled
1252 })
1253 }
1254
1255 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1260 let _event_handler = enter_event_handler_scope();
1261 let app_context = Rc::clone(&self.app_context);
1262 app_context.enter(|| {
1263 let handled = run_in_mutable_snapshot(|| {
1264 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1265 })
1266 .unwrap_or(false);
1267
1268 if handled {
1271 self.mark_dirty();
1272 }
1273
1274 handled
1275 })
1276 }
1277
1278 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1282 let app_context = Rc::clone(&self.app_context);
1283 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1284 }
1285
1286 pub fn ime_caret_geometry(
1290 &mut self,
1291 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1292 let app_context = Rc::clone(&self.app_context);
1293 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1294 }
1295
1296 pub fn clear_text_field_focus(&mut self) {
1299 let _event_handler = enter_event_handler_scope();
1300 let app_context = Rc::clone(&self.app_context);
1301 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1302 self.mark_dirty();
1303 self.request_layout_pass();
1304 }
1305
1306 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1309 let _event_handler = enter_event_handler_scope();
1310 let app_context = Rc::clone(&self.app_context);
1311 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1312 }
1313
1314 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1315 let handled = run_in_mutable_snapshot(|| {
1316 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1317 })
1318 .unwrap_or(false);
1319
1320 if handled {
1321 self.mark_dirty();
1322 self.request_layout_pass();
1323 }
1324
1325 handled
1326 }
1327}