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 }
75
76 pub fn set_pointer_source(&mut self, source: PointerSource) {
82 self.pointer_source = source;
83 }
84
85 pub fn pointer_source(&self) -> PointerSource {
87 self.pointer_source
88 }
89
90 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
91 self.set_cursor_at_time(x, y, None)
92 }
93
94 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
101 let event_time = self.realtime_pointer_event_time(time_ms);
102 self.set_cursor_at_event_time(x, y, event_time)
103 }
104
105 pub fn set_cursor_at_event_time(
107 &mut self,
108 x: f32,
109 y: f32,
110 event_time: PointerEventTime,
111 ) -> bool {
112 let _event_handler = enter_event_handler_scope();
113 let app_context = Rc::clone(&self.app_context);
114 let result = app_context.enter(|| {
115 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
116 });
117 if result {
118 self.mark_dirty();
119 }
120 log::trace!(
121 target: "cranpose::input",
122 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
123 event_time.platform_time_ms,
124 event_time.animation_time_nanos,
125 );
126 result
127 }
128
129 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
130 self.cursor = (x, y);
131
132 if self.buttons_pressed != PointerButtons::NONE {
136 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
137 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
138 if !targets.is_empty() {
139 let event = self
140 .pointer_event(
141 PointerEventKind::Move,
142 Point { x, y },
143 Point { x, y },
144 event_time,
145 )
146 .with_buttons(self.buttons_pressed)
147 .with_source(self.pointer_source);
148 self.dispatch_targets(targets, event, false);
149 return true;
150 }
151
152 return false;
153 }
154
155 return false;
158 }
159
160 let hits = self.renderer.scene().hit_test(x, y);
163 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
164
165 let pos = Point { x, y };
167 let previously_hovered = self.hovered_nodes.clone();
168 for old_id in previously_hovered {
169 if !new_ids.contains(&old_id) {
170 if let Some(target) = self.renderer.scene().find_target(old_id) {
171 let exit_event = self
172 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
173 .with_buttons(self.buttons_pressed)
174 .with_source(self.pointer_source);
175 self.dispatch_targets(std::iter::once(target), exit_event, false);
176 }
177 }
178 }
179
180 for hit in &hits {
182 if !self.hovered_nodes.contains(&hit.node_id()) {
183 let enter_event = self
184 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
185 .with_buttons(self.buttons_pressed)
186 .with_source(self.pointer_source);
187 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
188 }
189 }
190
191 self.hovered_nodes = new_ids;
192
193 if !hits.is_empty() {
194 let event = self
195 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
196 .with_buttons(self.buttons_pressed)
197 .with_source(self.pointer_source);
198 self.dispatch_targets(hits, event, true);
199 true
200 } else {
201 false
202 }
203 }
204
205 pub fn pointer_pressed(&mut self) -> bool {
206 self.pointer_pressed_at_time(None)
207 }
208
209 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
212 let event_time = self.realtime_pointer_event_time(time_ms);
213 self.pointer_pressed_at_event_time(event_time)
214 }
215
216 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
218 let _event_handler = enter_event_handler_scope();
219 let app_context = Rc::clone(&self.app_context);
220 let result = app_context.enter(|| {
221 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
222 });
223 if result {
224 self.mark_dirty();
225 }
226 log::trace!(
227 target: "cranpose::input",
228 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
229 event_time.platform_time_ms,
230 event_time.animation_time_nanos,
231 );
232 result
233 }
234
235 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
236 self.buttons_pressed.insert(PointerButton::Primary);
238
239 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
247 if hits.is_empty() {
248 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
249 false
250 } else {
251 let event = self
252 .pointer_event(
253 PointerEventKind::Down,
254 Point {
255 x: self.cursor.0,
256 y: self.cursor.1,
257 },
258 Point {
259 x: self.cursor.0,
260 y: self.cursor.1,
261 },
262 event_time,
263 )
264 .with_buttons(self.buttons_pressed)
265 .with_source(self.pointer_source);
266
267 let mut delivered_capture_paths = Vec::new();
268 let mut applier = self.composition.applier_mut();
269 for hit in hits {
270 let node_id = hit.node_id();
271 delivered_capture_paths.push(hit.capture_path());
272 hit.dispatch_with_applier(&mut applier, event.clone());
273 log::trace!(
274 target: "cranpose::input",
275 "dispatch {:?} node={} consumed={} stop_on_consume=true",
276 event.kind,
277 node_id,
278 event.is_consumed(),
279 );
280 if event.is_consumed() {
281 break;
282 }
283 }
284
285 self.hit_path_tracker
286 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
287 log::trace!(
288 target: "cranpose::input",
289 "pointer_pressed_inner cached_hit_path={:?}",
290 self.hit_path_tracker.get_path(PointerId::PRIMARY),
291 );
292
293 true
294 }
295 }
296
297 pub fn pointer_released(&mut self) -> bool {
298 self.pointer_released_at_time(None)
299 }
300
301 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
314 self.pointer_released_at_position_time(x, y, None)
315 }
316
317 pub fn pointer_released_at_position_time(
321 &mut self,
322 x: f32,
323 y: f32,
324 time_ms: Option<i64>,
325 ) -> bool {
326 let event_time = self.realtime_pointer_event_time(time_ms);
327 self.pointer_released_at_position_event_time(x, y, event_time)
328 }
329
330 pub fn pointer_released_at_position_event_time(
332 &mut self,
333 x: f32,
334 y: f32,
335 event_time: PointerEventTime,
336 ) -> bool {
337 let _event_handler = enter_event_handler_scope();
338 let app_context = Rc::clone(&self.app_context);
339 let result = app_context.enter(|| {
340 run_in_mutable_snapshot(|| {
341 self.cursor = (x, y);
342 self.pointer_released_inner(event_time)
343 })
344 .unwrap_or(false)
345 });
346 if result {
347 self.mark_dirty();
348 }
349 log::trace!(
350 target: "cranpose::input",
351 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
352 event_time.platform_time_ms,
353 event_time.animation_time_nanos,
354 );
355 result
356 }
357
358 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
361 let event_time = self.realtime_pointer_event_time(time_ms);
362 self.pointer_released_at_event_time(event_time)
363 }
364
365 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
367 let _event_handler = enter_event_handler_scope();
368 let app_context = Rc::clone(&self.app_context);
369 let result = app_context.enter(|| {
370 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
371 });
372 if result {
373 self.mark_dirty();
374 }
375 log::trace!(
376 target: "cranpose::input",
377 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
378 event_time.platform_time_ms,
379 event_time.animation_time_nanos,
380 );
381 result
382 }
383
384 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
385 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);
393
394 if !targets.is_empty() {
395 let event = self
396 .pointer_event(
397 PointerEventKind::Up,
398 Point {
399 x: self.cursor.0,
400 y: self.cursor.1,
401 },
402 Point {
403 x: self.cursor.0,
404 y: self.cursor.1,
405 },
406 event_time,
407 )
408 .with_buttons(corrected_buttons)
409 .with_source(self.pointer_source);
410
411 self.dispatch_targets(targets, event, false);
412 true
413 } else {
414 false
415 }
416 }
417
418 pub fn secondary_pointer_pressed(
427 &mut self,
428 pointer_id: u64,
429 x: f32,
430 y: f32,
431 time_ms: Option<i64>,
432 ) -> bool {
433 let event_time = self.realtime_pointer_event_time(time_ms);
434 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
435 }
436
437 pub fn secondary_pointer_moved(
439 &mut self,
440 pointer_id: u64,
441 x: f32,
442 y: f32,
443 time_ms: Option<i64>,
444 ) -> bool {
445 let event_time = self.realtime_pointer_event_time(time_ms);
446 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
447 }
448
449 pub fn secondary_pointer_released(
451 &mut self,
452 pointer_id: u64,
453 x: f32,
454 y: f32,
455 time_ms: Option<i64>,
456 ) -> bool {
457 let event_time = self.realtime_pointer_event_time(time_ms);
458 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
459 }
460
461 fn dispatch_secondary_pointer(
462 &mut self,
463 kind: PointerEventKind,
464 pointer_id: u64,
465 x: f32,
466 y: f32,
467 event_time: PointerEventTime,
468 ) -> bool {
469 if pointer_id == 0 {
470 log::warn!(
471 target: "cranpose::input",
472 "secondary pointer dispatch called with the primary pointer id"
473 );
474 return false;
475 }
476
477 let _event_handler = enter_event_handler_scope();
478 let app_context = Rc::clone(&self.app_context);
479 let result = app_context.enter(|| {
480 run_in_mutable_snapshot(|| {
481 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
482 return false;
483 }
484 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
485 if targets.is_empty() {
486 return false;
487 }
488 let pos = Point { x, y };
489 let event = self
490 .pointer_event(kind, pos, pos, event_time)
491 .with_buttons(self.buttons_pressed)
492 .with_id(pointer_id)
493 .with_source(self.pointer_source);
494 self.dispatch_targets(targets, event, false);
495 true
496 })
497 .unwrap_or(false)
498 });
499 if result {
500 self.mark_dirty();
501 }
502 log::trace!(
503 target: "cranpose::input",
504 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
505 event_time.platform_time_ms,
506 event_time.animation_time_nanos,
507 );
508 result
509 }
510
511 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
517 let event_time = self.realtime_pointer_event_time(None);
518 let _event_handler = enter_event_handler_scope();
519 let app_context = Rc::clone(&self.app_context);
520 let result = app_context.enter(|| {
521 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
522 .unwrap_or(false)
523 });
524 if result {
525 self.mark_dirty();
526 }
527 log::trace!(
528 target: "cranpose::input",
529 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
530 );
531 result
532 }
533
534 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
535 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
536 return false;
537 }
538
539 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
540 if hits.is_empty() {
541 return false;
542 }
543
544 let pos = Point {
545 x: self.cursor.0,
546 y: self.cursor.1,
547 };
548 let event = self
549 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
550 .with_buttons(self.buttons_pressed)
551 .with_zoom_delta(zoom_factor)
552 .with_source(self.pointer_source);
553
554 let capture_paths = hits
555 .iter()
556 .map(|hit| hit.capture_path())
557 .collect::<Vec<_>>();
558 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
559 .into_iter()
560 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
561 .collect::<Vec<_>>();
562
563 self.dispatch_targets(targets, event.clone(), true);
564
565 event.is_consumed()
566 }
567
568 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
572 let event_time = self.realtime_pointer_event_time(None);
573 let _event_handler = enter_event_handler_scope();
574 let app_context = Rc::clone(&self.app_context);
575 let result = app_context.enter(|| {
576 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
577 .unwrap_or(false)
578 });
579 if result {
580 self.mark_dirty();
581 }
582 log::trace!(
583 target: "cranpose::input",
584 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
585 );
586 result
587 }
588
589 fn pointer_scrolled_inner(
590 &mut self,
591 delta_x: f32,
592 delta_y: f32,
593 event_time: PointerEventTime,
594 ) -> bool {
595 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
596 return false;
597 }
598
599 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
600 if hits.is_empty() {
601 return false;
602 }
603
604 let event = self
605 .pointer_event(
606 PointerEventKind::Scroll,
607 Point {
608 x: self.cursor.0,
609 y: self.cursor.1,
610 },
611 Point {
612 x: self.cursor.0,
613 y: self.cursor.1,
614 },
615 event_time,
616 )
617 .with_buttons(self.buttons_pressed)
618 .with_scroll_delta(Point {
619 x: delta_x,
620 y: delta_y,
621 })
622 .with_source(self.pointer_source);
623
624 let capture_paths = hits
625 .iter()
626 .map(|hit| hit.capture_path())
627 .collect::<Vec<_>>();
628 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
629 .into_iter()
630 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
631 .collect::<Vec<_>>();
632
633 self.dispatch_targets(targets, event.clone(), true);
634
635 event.is_consumed()
636 }
637
638 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
649 where
650 F: Fn(RotaryScrollEvent) -> bool + 'static,
651 {
652 self.on_rotary_scroll = Some(Rc::new(handler));
653 }
654
655 pub fn clear_on_rotary_scroll(&mut self) {
657 self.on_rotary_scroll = None;
658 }
659
660 pub fn rotary_scroll_factor(&self) -> f32 {
663 self.rotary_scroll_factor
664 }
665
666 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
675 if factor.is_finite() && factor > 0.0 {
676 self.rotary_scroll_factor = factor;
677 }
678 }
679
680 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
686 let factor = self.rotary_scroll_factor;
687 self.rotary_scrolled(RotaryScrollEvent::from_detents(
688 detents,
689 factor,
690 factor,
691 uptime_millis,
692 ))
693 }
694
695 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
716 let _event_handler = enter_event_handler_scope();
717 let app_context = Rc::clone(&self.app_context);
718 let result = app_context.enter(|| {
719 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
720 });
721 if result {
722 self.mark_dirty();
723 }
724 log::trace!(
725 target: "cranpose::input",
726 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
727 event.vertical_scroll_pixels,
728 event.horizontal_scroll_pixels,
729 event.uptime_millis,
730 );
731 result
732 }
733
734 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
735 if rotary.is_empty() {
736 return false;
737 }
738
739 let bubble_order = self.rotary_dispatch_order();
742 let position = Point {
743 x: self.cursor.0,
744 y: self.cursor.1,
745 };
746
747 if !bubble_order.is_empty() {
748 let capture_targets = bubble_order
750 .iter()
751 .rev()
752 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
753 .collect::<Vec<_>>();
754 let capture_event =
755 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
756 self.dispatch_targets(capture_targets, capture_event.clone(), true);
757 if capture_event.is_consumed() {
758 return true;
759 }
760
761 let bubble_targets = bubble_order
763 .iter()
764 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
765 .collect::<Vec<_>>();
766 let bubble_event =
767 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
768 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
769 if bubble_event.is_consumed() {
770 return true;
771 }
772 }
773
774 if let Some(handler) = self.on_rotary_scroll.clone() {
776 return handler(rotary);
777 }
778
779 false
780 }
781
782 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
788 if let Some(focused) = cranpose_ui::active_focus_target() {
789 if let Some(target) = self.renderer.scene().find_target(focused) {
790 let path = target.capture_path();
791 if !path.is_empty() {
792 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
793 }
794 }
795 }
796
797 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
798 if hits.is_empty() {
799 return Vec::new();
800 }
801 let capture_paths = hits
802 .iter()
803 .map(|hit| hit.capture_path())
804 .collect::<Vec<_>>();
805 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
806 }
807
808 pub fn cancel_gesture(&mut self) {
814 let event_time = self.realtime_pointer_event_time(None);
815 let _event_handler = enter_event_handler_scope();
816 let app_context = Rc::clone(&self.app_context);
817 let _ = app_context.enter(|| {
818 run_in_mutable_snapshot(|| {
819 self.cancel_gesture_inner(event_time);
820 })
821 });
822 }
823
824 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
825 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
826
827 self.hit_path_tracker.clear();
829 self.buttons_pressed = PointerButtons::NONE;
830
831 if !targets.is_empty() {
832 let event = self
833 .pointer_event(
834 PointerEventKind::Cancel,
835 Point {
836 x: self.cursor.0,
837 y: self.cursor.1,
838 },
839 Point {
840 x: self.cursor.0,
841 y: self.cursor.1,
842 },
843 event_time,
844 )
845 .with_source(self.pointer_source);
846
847 self.dispatch_targets(targets, event, false);
848 }
849
850 let pos = Point {
852 x: self.cursor.0,
853 y: self.cursor.1,
854 };
855 let hovered_nodes = self.hovered_nodes.clone();
856 for node_id in hovered_nodes {
857 if let Some(target) = self.renderer.scene().find_target(node_id) {
858 let exit_event = self
859 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
860 .with_source(self.pointer_source);
861 self.dispatch_targets(std::iter::once(target), exit_event, false);
862 }
863 }
864 self.hovered_nodes.clear();
865 }
866
867 pub fn set_platform_text_input(
874 &mut self,
875 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
876 ) {
877 let app_context = Rc::clone(&self.app_context);
878 app_context
879 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
880 }
881
882 pub fn clear_platform_text_input(&mut self) {
884 let app_context = Rc::clone(&self.app_context);
885 app_context.enter(cranpose_ui::text_input_session::clear_platform_text_input_handler);
886 }
887
888 pub fn notify_app_paused(&mut self) {
895 let app_context = Rc::clone(&self.app_context);
896 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
897 }
898
899 pub fn notify_app_resumed(&mut self) -> bool {
907 let app_context = Rc::clone(&self.app_context);
908 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
909 }
910
911 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
918 let _event_handler = enter_event_handler_scope();
919 let app_context = Rc::clone(&self.app_context);
920 app_context.enter(|| self.on_key_event_inner(event))
921 }
922
923 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
925 use KeyEventType::KeyDown;
926
927 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
929 #[cfg(all(
930 feature = "clipboard-native",
931 not(target_arch = "wasm32"),
932 not(target_os = "android"),
933 not(target_os = "ios")
934 ))]
935 {
936 match event.key_code {
937 KeyCode::C => {
939 if let Some(text) = self.on_copy_inner() {
940 cranpose_ui::clipboard_session::clipboard_write_text(&text);
941 return true;
942 }
943 }
944 KeyCode::X => {
946 if let Some(text) = self.on_cut_inner() {
947 cranpose_ui::clipboard_session::clipboard_write_text(&text);
948 self.mark_dirty();
949 self.request_layout_pass();
950 return true;
951 }
952 }
953 KeyCode::V => {
955 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text() {
956 if self.on_paste_inner(&text) {
957 return true;
958 }
959 }
960 }
961 _ => {}
962 }
963 }
964 }
965
966 if !cranpose_ui::text_field_focus::has_focused_field() {
968 return false;
969 }
970
971 let handled = run_in_mutable_snapshot(|| {
975 cranpose_ui::text_field_focus::dispatch_key_event(event)
978 })
979 .unwrap_or(false);
980
981 if handled {
982 self.mark_dirty();
984 self.request_layout_pass();
985 }
986
987 handled
988 }
989
990 pub fn on_paste(&mut self, text: &str) -> bool {
994 let _event_handler = enter_event_handler_scope();
995 let app_context = Rc::clone(&self.app_context);
996 app_context.enter(|| self.on_paste_inner(text))
997 }
998
999 fn on_paste_inner(&mut self, text: &str) -> bool {
1000 let handled =
1004 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
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_copy(&mut self) -> Option<String> {
1019 let app_context = Rc::clone(&self.app_context);
1020 app_context.enter(|| self.on_copy_inner())
1021 }
1022
1023 fn on_copy_inner(&mut self) -> Option<String> {
1024 cranpose_ui::text_field_focus::dispatch_copy()
1026 }
1027
1028 pub fn on_cut(&mut self) -> Option<String> {
1032 let _event_handler = enter_event_handler_scope();
1033 let app_context = Rc::clone(&self.app_context);
1034 app_context.enter(|| self.on_cut_inner())
1035 }
1036
1037 fn on_cut_inner(&mut self) -> Option<String> {
1038 let text =
1039 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1040
1041 if text.is_some() {
1042 self.mark_dirty();
1043 self.request_layout_pass();
1044 }
1045
1046 text
1047 }
1048
1049 #[cfg(all(
1053 feature = "clipboard-native",
1054 target_os = "linux",
1055 not(target_arch = "wasm32")
1056 ))]
1057 pub fn set_primary_selection(&mut self, text: &str) {
1058 use arboard::{LinuxClipboardKind, SetExtLinux};
1059 if let Some(ref mut clipboard) = self.clipboard {
1060 let result = clipboard
1061 .set()
1062 .clipboard(LinuxClipboardKind::Primary)
1063 .text(text.to_string());
1064 if let Err(e) = result {
1065 log::debug!("Primary selection set failed: {:?}", e);
1067 }
1068 }
1069 }
1070
1071 #[cfg(not(all(
1072 feature = "clipboard-native",
1073 target_os = "linux",
1074 not(target_arch = "wasm32")
1075 )))]
1076 pub fn set_primary_selection(&mut self, _text: &str) {}
1077
1078 #[cfg(all(
1081 feature = "clipboard-native",
1082 target_os = "linux",
1083 not(target_arch = "wasm32")
1084 ))]
1085 pub fn get_primary_selection(&mut self) -> Option<String> {
1086 use arboard::{GetExtLinux, LinuxClipboardKind};
1087 if let Some(ref mut clipboard) = self.clipboard {
1088 clipboard
1089 .get()
1090 .clipboard(LinuxClipboardKind::Primary)
1091 .text()
1092 .ok()
1093 } else {
1094 None
1095 }
1096 }
1097
1098 #[cfg(not(all(
1099 feature = "clipboard-native",
1100 target_os = "linux",
1101 not(target_arch = "wasm32")
1102 )))]
1103 pub fn get_primary_selection(&mut self) -> Option<String> {
1104 None
1105 }
1106
1107 pub fn sync_selection_to_primary(&mut self) {
1110 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1111 {
1112 if let Some(text) = self.on_copy() {
1113 self.set_primary_selection(&text);
1114 }
1115 }
1116 }
1117
1118 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1126 let _event_handler = enter_event_handler_scope();
1127 let app_context = Rc::clone(&self.app_context);
1128 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1129 }
1130
1131 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1132 let handled = run_in_mutable_snapshot(|| {
1134 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1135 })
1136 .unwrap_or(false);
1137
1138 if handled {
1139 self.mark_dirty();
1140 self.request_layout_pass();
1142 }
1143
1144 handled
1145 }
1146
1147 pub fn on_ime_finish_composing(&mut self) -> bool {
1151 let _event_handler = enter_event_handler_scope();
1152 let app_context = Rc::clone(&self.app_context);
1153 app_context.enter(|| self.on_ime_finish_composing_inner())
1154 }
1155
1156 fn on_ime_finish_composing_inner(&mut self) -> bool {
1157 let handled =
1158 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1159 .unwrap_or(false);
1160
1161 if handled {
1162 self.mark_dirty();
1163 self.request_layout_pass();
1164 }
1165
1166 handled
1167 }
1168
1169 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1173 let _event_handler = enter_event_handler_scope();
1174 let app_context = Rc::clone(&self.app_context);
1175 app_context.enter(|| {
1176 let handled = run_in_mutable_snapshot(|| {
1177 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1178 start_bytes,
1179 end_bytes,
1180 )
1181 })
1182 .unwrap_or(false);
1183
1184 if handled {
1185 self.mark_dirty();
1186 self.request_layout_pass();
1187 }
1188
1189 handled
1190 })
1191 }
1192
1193 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1198 let _event_handler = enter_event_handler_scope();
1199 let app_context = Rc::clone(&self.app_context);
1200 app_context.enter(|| {
1201 let handled = run_in_mutable_snapshot(|| {
1202 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1203 })
1204 .unwrap_or(false);
1205
1206 if handled {
1209 self.mark_dirty();
1210 }
1211
1212 handled
1213 })
1214 }
1215
1216 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1220 let app_context = Rc::clone(&self.app_context);
1221 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1222 }
1223
1224 pub fn ime_caret_geometry(
1228 &mut self,
1229 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1230 let app_context = Rc::clone(&self.app_context);
1231 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1232 }
1233
1234 pub fn clear_text_field_focus(&mut self) {
1237 let _event_handler = enter_event_handler_scope();
1238 let app_context = Rc::clone(&self.app_context);
1239 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1240 self.mark_dirty();
1241 self.request_layout_pass();
1242 }
1243
1244 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1247 let _event_handler = enter_event_handler_scope();
1248 let app_context = Rc::clone(&self.app_context);
1249 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1250 }
1251
1252 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1253 let handled = run_in_mutable_snapshot(|| {
1254 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1255 })
1256 .unwrap_or(false);
1257
1258 if handled {
1259 self.mark_dirty();
1260 self.request_layout_pass();
1261 }
1262
1263 handled
1264 }
1265}