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 fn dispatch_to_hits(
74 &mut self,
75 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
76 event: PointerEvent,
77 ) -> bool {
78 let capture_paths = hits
79 .iter()
80 .map(|hit| hit.capture_path())
81 .collect::<Vec<_>>();
82 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
83 .into_iter()
84 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
85 .collect::<Vec<_>>();
86
87 self.dispatch_targets(targets, event.clone(), true);
88
89 event.is_consumed()
90 }
91
92 pub fn set_pointer_source(&mut self, source: PointerSource) {
98 self.pointer_source = source;
99 }
100
101 pub fn pointer_source(&self) -> PointerSource {
103 self.pointer_source
104 }
105
106 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
115 self.modifiers = Some(modifiers);
116 }
117
118 pub fn modifiers(&self) -> Option<Modifiers> {
122 self.modifiers
123 }
124
125 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
126 self.set_cursor_at_time(x, y, None)
127 }
128
129 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
136 let event_time = self.realtime_pointer_event_time(time_ms);
137 self.set_cursor_at_event_time(x, y, event_time)
138 }
139
140 pub fn set_cursor_at_event_time(
142 &mut self,
143 x: f32,
144 y: f32,
145 event_time: PointerEventTime,
146 ) -> bool {
147 let _event_handler = enter_event_handler_scope();
148 let app_context = Rc::clone(&self.app_context);
149 let result = app_context.enter(|| {
150 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
151 });
152 if result {
153 self.mark_dirty();
154 }
155 log::trace!(
156 target: "cranpose::input",
157 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
158 event_time.platform_time_ms,
159 event_time.animation_time_nanos,
160 );
161 result
162 }
163
164 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
165 self.cursor = (x, y);
166
167 if self.buttons_pressed != PointerButtons::NONE {
168 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
169 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
170 if !targets.is_empty() {
171 let event = self
172 .pointer_event(
173 PointerEventKind::Move,
174 Point { x, y },
175 Point { x, y },
176 event_time,
177 )
178 .with_buttons(self.buttons_pressed)
179 .with_source(self.pointer_source);
180 self.dispatch_targets(targets, event, false);
181 return true;
182 }
183
184 return false;
185 }
186
187 return false;
188 }
189
190 let hits = self.renderer.scene().hit_test(x, y);
191 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
192
193 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 {
208 if !self.hovered_nodes.contains(&hit.node_id()) {
209 let enter_event = self
210 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
211 .with_buttons(self.buttons_pressed)
212 .with_source(self.pointer_source);
213 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
214 }
215 }
216
217 self.hovered_nodes = new_ids;
218
219 if !hits.is_empty() {
220 let event = self
221 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
222 .with_buttons(self.buttons_pressed)
223 .with_source(self.pointer_source);
224 self.dispatch_targets(hits, event, true);
225 true
226 } else {
227 false
228 }
229 }
230
231 pub fn pointer_pressed(&mut self) -> bool {
232 self.pointer_pressed_at_time(None)
233 }
234
235 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
238 let event_time = self.realtime_pointer_event_time(time_ms);
239 self.pointer_pressed_at_event_time(event_time)
240 }
241
242 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
244 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
245 return true;
246 }
247 let _event_handler = enter_event_handler_scope();
248 let app_context = Rc::clone(&self.app_context);
249 let result = app_context.enter(|| {
250 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
251 });
252 if result {
253 self.mark_dirty();
254 }
255 log::trace!(
256 target: "cranpose::input",
257 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
258 event_time.platform_time_ms,
259 event_time.animation_time_nanos,
260 );
261 result
262 }
263
264 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
265 self.buttons_pressed.insert(PointerButton::Primary);
266
267 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
268 if hits.is_empty() {
269 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
270 false
271 } else {
272 let event = self
273 .pointer_event(
274 PointerEventKind::Down,
275 Point {
276 x: self.cursor.0,
277 y: self.cursor.1,
278 },
279 Point {
280 x: self.cursor.0,
281 y: self.cursor.1,
282 },
283 event_time,
284 )
285 .with_buttons(self.buttons_pressed)
286 .with_source(self.pointer_source);
287
288 let mut delivered_capture_paths = Vec::new();
289 let mut applier = self.composition.applier_mut();
290 for hit in hits {
291 let node_id = hit.node_id();
292 delivered_capture_paths.push(hit.capture_path());
293 hit.dispatch_with_applier(&mut applier, event.clone());
294 log::trace!(
295 target: "cranpose::input",
296 "dispatch {:?} node={} consumed={} stop_on_consume=true",
297 event.kind,
298 node_id,
299 event.is_consumed(),
300 );
301 if event.is_consumed() {
302 break;
303 }
304 }
305
306 self.hit_path_tracker
307 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
308 log::trace!(
309 target: "cranpose::input",
310 "pointer_pressed_inner cached_hit_path={:?}",
311 self.hit_path_tracker.get_path(PointerId::PRIMARY),
312 );
313
314 true
315 }
316 }
317
318 pub fn pointer_released(&mut self) -> bool {
319 self.pointer_released_at_time(None)
320 }
321
322 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
335 self.pointer_released_at_position_time(x, y, None)
336 }
337
338 pub fn pointer_released_at_position_time(
342 &mut self,
343 x: f32,
344 y: f32,
345 time_ms: Option<i64>,
346 ) -> bool {
347 let event_time = self.realtime_pointer_event_time(time_ms);
348 self.pointer_released_at_position_event_time(x, y, event_time)
349 }
350
351 pub fn pointer_released_at_position_event_time(
353 &mut self,
354 x: f32,
355 y: f32,
356 event_time: PointerEventTime,
357 ) -> bool {
358 let _event_handler = enter_event_handler_scope();
359 let app_context = Rc::clone(&self.app_context);
360 let result = app_context.enter(|| {
361 run_in_mutable_snapshot(|| {
362 self.cursor = (x, y);
363 self.pointer_released_inner(event_time)
364 })
365 .unwrap_or(false)
366 });
367 if result {
368 self.mark_dirty();
369 }
370 log::trace!(
371 target: "cranpose::input",
372 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
373 event_time.platform_time_ms,
374 event_time.animation_time_nanos,
375 );
376 result
377 }
378
379 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
382 let event_time = self.realtime_pointer_event_time(time_ms);
383 self.pointer_released_at_event_time(event_time)
384 }
385
386 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
388 let _event_handler = enter_event_handler_scope();
389 let app_context = Rc::clone(&self.app_context);
390 let result = app_context.enter(|| {
391 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
392 });
393 if result {
394 self.mark_dirty();
395 }
396 log::trace!(
397 target: "cranpose::input",
398 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
399 event_time.platform_time_ms,
400 event_time.animation_time_nanos,
401 );
402 result
403 }
404
405 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
406 self.buttons_pressed.remove(PointerButton::Primary);
407 let corrected_buttons = self.buttons_pressed;
408 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
409
410 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
411
412 if !targets.is_empty() {
413 let event = self
414 .pointer_event(
415 PointerEventKind::Up,
416 Point {
417 x: self.cursor.0,
418 y: self.cursor.1,
419 },
420 Point {
421 x: self.cursor.0,
422 y: self.cursor.1,
423 },
424 event_time,
425 )
426 .with_buttons(corrected_buttons)
427 .with_source(self.pointer_source);
428
429 self.dispatch_targets(targets, event, false);
430 true
431 } else {
432 false
433 }
434 }
435
436 pub fn secondary_pointer_pressed(
445 &mut self,
446 pointer_id: u64,
447 x: f32,
448 y: f32,
449 time_ms: Option<i64>,
450 ) -> bool {
451 let event_time = self.realtime_pointer_event_time(time_ms);
452 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
453 }
454
455 pub fn secondary_pointer_moved(
457 &mut self,
458 pointer_id: u64,
459 x: f32,
460 y: f32,
461 time_ms: Option<i64>,
462 ) -> bool {
463 let event_time = self.realtime_pointer_event_time(time_ms);
464 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
465 }
466
467 pub fn secondary_pointer_released(
469 &mut self,
470 pointer_id: u64,
471 x: f32,
472 y: f32,
473 time_ms: Option<i64>,
474 ) -> bool {
475 let event_time = self.realtime_pointer_event_time(time_ms);
476 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
477 }
478
479 fn dispatch_secondary_pointer(
480 &mut self,
481 kind: PointerEventKind,
482 pointer_id: u64,
483 x: f32,
484 y: f32,
485 event_time: PointerEventTime,
486 ) -> bool {
487 if pointer_id == 0 {
488 log::warn!(
489 target: "cranpose::input",
490 "secondary pointer dispatch called with the primary pointer id"
491 );
492 return false;
493 }
494
495 let _event_handler = enter_event_handler_scope();
496 let app_context = Rc::clone(&self.app_context);
497 let result = app_context.enter(|| {
498 run_in_mutable_snapshot(|| {
499 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
500 return false;
501 }
502 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
503 if targets.is_empty() {
504 return false;
505 }
506 let pos = Point { x, y };
507 let event = self
508 .pointer_event(kind, pos, pos, event_time)
509 .with_buttons(self.buttons_pressed)
510 .with_id(pointer_id)
511 .with_source(self.pointer_source);
512 self.dispatch_targets(targets, event, false);
513 true
514 })
515 .unwrap_or(false)
516 });
517 if result {
518 self.mark_dirty();
519 }
520 log::trace!(
521 target: "cranpose::input",
522 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
523 event_time.platform_time_ms,
524 event_time.animation_time_nanos,
525 );
526 result
527 }
528
529 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
535 let event_time = self.realtime_pointer_event_time(None);
536 let _event_handler = enter_event_handler_scope();
537 let app_context = Rc::clone(&self.app_context);
538 let result = app_context.enter(|| {
539 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
540 .unwrap_or(false)
541 });
542 if result {
543 self.mark_dirty();
544 }
545 log::trace!(
546 target: "cranpose::input",
547 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
548 );
549 result
550 }
551
552 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
553 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
554 return false;
555 }
556
557 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
558 if hits.is_empty() {
559 return false;
560 }
561
562 let pos = Point {
563 x: self.cursor.0,
564 y: self.cursor.1,
565 };
566 let event = self
567 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
568 .with_buttons(self.buttons_pressed)
569 .with_zoom_delta(zoom_factor)
570 .with_source(self.pointer_source);
571
572 self.dispatch_to_hits(&hits, event)
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 self.dispatch_to_hits(&hits, event)
687 }
688
689 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
700 where
701 F: Fn(RotaryScrollEvent) -> bool + 'static,
702 {
703 self.on_rotary_scroll = Some(Rc::new(handler));
704 }
705
706 pub fn clear_on_rotary_scroll(&mut self) {
708 self.on_rotary_scroll = None;
709 }
710
711 pub fn rotary_scroll_factor(&self) -> f32 {
714 self.rotary_scroll_factor
715 }
716
717 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
726 if factor.is_finite() && factor > 0.0 {
727 self.rotary_scroll_factor = factor;
728 }
729 }
730
731 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
737 let factor = self.rotary_scroll_factor;
738 self.rotary_scrolled(RotaryScrollEvent::from_detents(
739 detents,
740 factor,
741 factor,
742 uptime_millis,
743 ))
744 }
745
746 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
767 let _event_handler = enter_event_handler_scope();
768 let app_context = Rc::clone(&self.app_context);
769 let result = app_context.enter(|| {
770 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
771 });
772 if result {
773 self.mark_dirty();
774 }
775 log::trace!(
776 target: "cranpose::input",
777 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
778 event.vertical_scroll_pixels,
779 event.horizontal_scroll_pixels,
780 event.uptime_millis,
781 );
782 result
783 }
784
785 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
786 if rotary.is_empty() {
787 return false;
788 }
789
790 let bubble_order = self.rotary_dispatch_order();
791 let position = Point {
792 x: self.cursor.0,
793 y: self.cursor.1,
794 };
795
796 if !bubble_order.is_empty() {
797 let capture_targets = bubble_order
798 .iter()
799 .rev()
800 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
801 .collect::<Vec<_>>();
802 let mut capture_event =
803 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
804 capture_event.modifiers = self.modifiers;
805 self.dispatch_targets(capture_targets, capture_event.clone(), true);
806 if capture_event.is_consumed() {
807 return true;
808 }
809
810 let bubble_targets = bubble_order
811 .iter()
812 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
813 .collect::<Vec<_>>();
814 let mut bubble_event =
815 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
816 bubble_event.modifiers = self.modifiers;
817 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
818 if bubble_event.is_consumed() {
819 return true;
820 }
821 }
822
823 if let Some(handler) = self.on_rotary_scroll.clone() {
824 return handler(rotary);
825 }
826
827 false
828 }
829
830 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
831 if let Some(focused) = cranpose_ui::active_focus_target()
832 && let Some(target) = self.renderer.scene().find_target(focused)
833 {
834 let path = target.capture_path();
835 if !path.is_empty() {
836 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
837 }
838 }
839
840 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
841 if hits.is_empty() {
842 return Vec::new();
843 }
844 let capture_paths = hits
845 .iter()
846 .map(|hit| hit.capture_path())
847 .collect::<Vec<_>>();
848 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
849 }
850
851 pub fn cancel_gesture(&mut self) {
857 let event_time = self.realtime_pointer_event_time(None);
858 let _event_handler = enter_event_handler_scope();
859 let app_context = Rc::clone(&self.app_context);
860 let _ = app_context.enter(|| {
861 run_in_mutable_snapshot(|| {
862 self.cancel_gesture_inner(event_time);
863 })
864 });
865 }
866
867 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
868 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
869
870 self.hit_path_tracker.clear();
871 self.buttons_pressed = PointerButtons::NONE;
872
873 if !targets.is_empty() {
874 let event = self
875 .pointer_event(
876 PointerEventKind::Cancel,
877 Point {
878 x: self.cursor.0,
879 y: self.cursor.1,
880 },
881 Point {
882 x: self.cursor.0,
883 y: self.cursor.1,
884 },
885 event_time,
886 )
887 .with_source(self.pointer_source);
888
889 self.dispatch_targets(targets, event, false);
890 }
891
892 let pos = Point {
893 x: self.cursor.0,
894 y: self.cursor.1,
895 };
896 let hovered_nodes = self.hovered_nodes.clone();
897 for node_id in hovered_nodes {
898 if let Some(target) = self.renderer.scene().find_target(node_id) {
899 let exit_event = self
900 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
901 .with_source(self.pointer_source);
902 self.dispatch_targets(std::iter::once(target), exit_event, false);
903 }
904 }
905 self.hovered_nodes.clear();
906 }
907
908 pub fn set_platform_text_input(
915 &mut self,
916 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
917 ) {
918 let app_context = Rc::clone(&self.app_context);
919 app_context
920 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
921 }
922
923 pub fn notify_app_paused(&mut self) {
930 let app_context = Rc::clone(&self.app_context);
931 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
932 }
933
934 pub fn notify_app_resumed(&mut self) -> bool {
942 let app_context = Rc::clone(&self.app_context);
943 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
944 }
945
946 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
953 let _event_handler = enter_event_handler_scope();
954 let app_context = Rc::clone(&self.app_context);
955 app_context.enter(|| self.on_key_event_inner(event))
956 }
957
958 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
959 use KeyEventType::KeyDown;
960
961 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
962 #[cfg(all(
963 feature = "clipboard-native",
964 not(target_arch = "wasm32"),
965 not(target_os = "android"),
966 not(target_os = "ios")
967 ))]
968 {
969 match event.key_code {
970 KeyCode::C => {
971 if let Some(text) = self.on_copy_inner() {
972 cranpose_ui::clipboard_session::clipboard_write_text(&text);
973 return true;
974 }
975 }
976 KeyCode::X => {
977 if let Some(text) = self.on_cut_inner() {
978 cranpose_ui::clipboard_session::clipboard_write_text(&text);
979 self.mark_dirty();
980 self.request_layout_pass();
981 return true;
982 }
983 }
984 KeyCode::V => {
985 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
986 && self.on_paste_inner(&text)
987 {
988 return true;
989 }
990 }
991 _ => {}
992 }
993 }
994 }
995
996 if !cranpose_ui::text_field_focus::has_focused_field() {
997 return false;
998 }
999
1000 let handled =
1001 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1002 .unwrap_or(false);
1003
1004 if handled {
1005 self.mark_dirty();
1006 self.request_layout_pass();
1007 }
1008
1009 handled
1010 }
1011
1012 pub fn on_paste(&mut self, text: &str) -> bool {
1016 let _event_handler = enter_event_handler_scope();
1017 let app_context = Rc::clone(&self.app_context);
1018 app_context.enter(|| self.on_paste_inner(text))
1019 }
1020
1021 fn on_paste_inner(&mut self, text: &str) -> bool {
1022 let handled =
1023 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1024 .unwrap_or(false);
1025
1026 if handled {
1027 self.mark_dirty();
1028 self.request_layout_pass();
1029 }
1030
1031 handled
1032 }
1033
1034 pub fn on_copy(&mut self) -> Option<String> {
1038 let app_context = Rc::clone(&self.app_context);
1039 app_context.enter(|| self.on_copy_inner())
1040 }
1041
1042 fn on_copy_inner(&mut self) -> Option<String> {
1043 cranpose_ui::text_field_focus::dispatch_copy()
1044 }
1045
1046 pub fn on_cut(&mut self) -> Option<String> {
1050 let _event_handler = enter_event_handler_scope();
1051 let app_context = Rc::clone(&self.app_context);
1052 app_context.enter(|| self.on_cut_inner())
1053 }
1054
1055 fn on_cut_inner(&mut self) -> Option<String> {
1056 let text =
1057 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1058
1059 if text.is_some() {
1060 self.mark_dirty();
1061 self.request_layout_pass();
1062 }
1063
1064 text
1065 }
1066
1067 #[cfg(all(
1071 feature = "clipboard-native",
1072 target_os = "linux",
1073 not(target_arch = "wasm32")
1074 ))]
1075 pub fn set_primary_selection(&mut self, text: &str) {
1076 use arboard::{LinuxClipboardKind, SetExtLinux};
1077 if let Some(ref mut clipboard) = self.clipboard {
1078 let result = clipboard
1079 .set()
1080 .clipboard(LinuxClipboardKind::Primary)
1081 .text(text.to_string());
1082 if let Err(e) = result {
1083 log::debug!("Primary selection set failed: {:?}", e);
1084 }
1085 }
1086 }
1087
1088 #[cfg(not(all(
1089 feature = "clipboard-native",
1090 target_os = "linux",
1091 not(target_arch = "wasm32")
1092 )))]
1093 pub fn set_primary_selection(&mut self, _text: &str) {}
1094
1095 #[cfg(all(
1098 feature = "clipboard-native",
1099 target_os = "linux",
1100 not(target_arch = "wasm32")
1101 ))]
1102 pub fn get_primary_selection(&mut self) -> Option<String> {
1103 use arboard::{GetExtLinux, LinuxClipboardKind};
1104 if let Some(ref mut clipboard) = self.clipboard {
1105 clipboard
1106 .get()
1107 .clipboard(LinuxClipboardKind::Primary)
1108 .text()
1109 .ok()
1110 } else {
1111 None
1112 }
1113 }
1114
1115 #[cfg(not(all(
1116 feature = "clipboard-native",
1117 target_os = "linux",
1118 not(target_arch = "wasm32")
1119 )))]
1120 pub fn get_primary_selection(&mut self) -> Option<String> {
1121 None
1122 }
1123
1124 pub fn sync_selection_to_primary(&mut self) {
1127 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1128 {
1129 if let Some(text) = self.on_copy() {
1130 self.set_primary_selection(&text);
1131 }
1132 }
1133 }
1134
1135 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1143 let _event_handler = enter_event_handler_scope();
1144 let app_context = Rc::clone(&self.app_context);
1145 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1146 }
1147
1148 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1149 let handled = run_in_mutable_snapshot(|| {
1150 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1151 })
1152 .unwrap_or(false);
1153
1154 if handled {
1155 self.mark_dirty();
1156 self.request_layout_pass();
1157 }
1158
1159 handled
1160 }
1161
1162 pub fn on_ime_finish_composing(&mut self) -> bool {
1166 let _event_handler = enter_event_handler_scope();
1167 let app_context = Rc::clone(&self.app_context);
1168 app_context.enter(|| self.on_ime_finish_composing_inner())
1169 }
1170
1171 fn on_ime_finish_composing_inner(&mut self) -> bool {
1172 let handled =
1173 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1174 .unwrap_or(false);
1175
1176 if handled {
1177 self.mark_dirty();
1178 self.request_layout_pass();
1179 }
1180
1181 handled
1182 }
1183
1184 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1188 let _event_handler = enter_event_handler_scope();
1189 let app_context = Rc::clone(&self.app_context);
1190 app_context.enter(|| {
1191 let handled = run_in_mutable_snapshot(|| {
1192 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1193 start_bytes,
1194 end_bytes,
1195 )
1196 })
1197 .unwrap_or(false);
1198
1199 if handled {
1200 self.mark_dirty();
1201 self.request_layout_pass();
1202 }
1203
1204 handled
1205 })
1206 }
1207
1208 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1213 let _event_handler = enter_event_handler_scope();
1214 let app_context = Rc::clone(&self.app_context);
1215 app_context.enter(|| {
1216 let handled = run_in_mutable_snapshot(|| {
1217 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1218 })
1219 .unwrap_or(false);
1220
1221 if handled {
1222 self.mark_dirty();
1223 }
1224
1225 handled
1226 })
1227 }
1228
1229 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1233 let app_context = Rc::clone(&self.app_context);
1234 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1235 }
1236
1237 pub fn ime_caret_geometry(
1241 &mut self,
1242 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1243 let app_context = Rc::clone(&self.app_context);
1244 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1245 }
1246
1247 pub fn clear_text_field_focus(&mut self) {
1250 let _event_handler = enter_event_handler_scope();
1251 let app_context = Rc::clone(&self.app_context);
1252 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1253 self.mark_dirty();
1254 self.request_layout_pass();
1255 }
1256
1257 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1260 let _event_handler = enter_event_handler_scope();
1261 let app_context = Rc::clone(&self.app_context);
1262 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1263 }
1264
1265 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1266 let handled = run_in_mutable_snapshot(|| {
1267 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1268 })
1269 .unwrap_or(false);
1270
1271 if handled {
1272 self.mark_dirty();
1273 self.request_layout_pass();
1274 }
1275
1276 handled
1277 }
1278}