1use cranpose_ui::FocusDirection;
2
3use super::*;
4
5impl<R> AppShell<R>
6where
7 R: Renderer,
8 R::Error: Debug,
9{
10 fn pointer_event(
11 &self,
12 kind: PointerEventKind,
13 position: Point,
14 global_position: Point,
15 event_time: PointerEventTime,
16 ) -> PointerEvent {
17 let mut event = PointerEvent::new(kind, position, global_position)
18 .with_time_ms(event_time.platform_time_ms)
19 .with_animation_time_nanos(event_time.animation_time_nanos);
20 event.modifiers = self.modifiers;
21 event
22 }
23
24 fn resolve_gesture_targets(
25 &self,
26 pointer: PointerId,
27 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
28 self.resolve_hit_path(pointer)
29 }
30
31 fn resolve_hit_path(
32 &self,
33 pointer: PointerId,
34 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
35 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
36 return Vec::new();
37 };
38
39 let scene = self.renderer.scene();
40 let targets: Vec<_> = node_ids
41 .iter()
42 .filter_map(|&id| scene.find_target(id))
43 .collect();
44 log::trace!(
45 target: "cranpose::input",
46 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
47 targets.len()
48 );
49 targets
50 }
51
52 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
53 where
54 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
55 {
56 let mut applier = self.composition.applier_mut();
57 for target in targets {
58 let node_id = target.node_id();
59 target.dispatch_with_applier(&mut applier, event.clone());
60 log::trace!(
61 target: "cranpose::input",
62 "dispatch {:?} node={} consumed={} stop_on_consume={}",
63 event.kind,
64 node_id,
65 event.is_consumed(),
66 stop_on_consume,
67 );
68 if stop_on_consume && event.is_consumed() {
69 break;
70 }
71 }
72 event.finish_post_dispatch();
73 }
74
75 fn dispatch_to_hits(
76 &mut self,
77 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
78 event: PointerEvent,
79 ) -> bool {
80 let capture_paths = hits
81 .iter()
82 .map(|hit| hit.capture_path())
83 .collect::<Vec<_>>();
84 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
85 .into_iter()
86 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
87 .collect::<Vec<_>>();
88
89 self.dispatch_targets(targets, event.clone(), true);
90
91 event.is_consumed()
92 }
93
94 pub fn set_pointer_source(&mut self, source: PointerSource) {
100 self.pointer_source = source;
101 }
102
103 pub fn pointer_source(&self) -> PointerSource {
105 self.pointer_source
106 }
107
108 pub fn set_modifiers(&mut self, modifiers: Modifiers) {
117 self.modifiers = Some(modifiers);
118 }
119
120 pub fn modifiers(&self) -> Option<Modifiers> {
124 self.modifiers
125 }
126
127 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
128 self.set_cursor_at_time(x, y, None)
129 }
130
131 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
138 let event_time = self.realtime_pointer_event_time(time_ms);
139 self.set_cursor_at_event_time(x, y, event_time)
140 }
141
142 pub fn set_cursor_at_event_time(
144 &mut self,
145 x: f32,
146 y: f32,
147 event_time: PointerEventTime,
148 ) -> bool {
149 let _event_handler = enter_event_handler_scope();
150 let app_context = Rc::clone(&self.app_context);
151 let result = app_context.enter(|| {
152 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
153 });
154 if result {
155 self.mark_dirty();
156 }
157 log::trace!(
158 target: "cranpose::input",
159 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
160 event_time.platform_time_ms,
161 event_time.animation_time_nanos,
162 );
163 result
164 }
165
166 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
167 self.cursor = (x, y);
168
169 if self.buttons_pressed != PointerButtons::NONE {
170 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
171 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
172 if !targets.is_empty() {
173 let event = self
174 .pointer_event(
175 PointerEventKind::Move,
176 Point { x, y },
177 Point { x, y },
178 event_time,
179 )
180 .with_buttons(self.buttons_pressed)
181 .with_source(self.pointer_source);
182 self.dispatch_targets(targets, event, false);
183 return true;
184 }
185
186 return false;
187 }
188
189 return false;
190 }
191
192 let hits = self.renderer.scene().hit_test(x, y);
193 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
194
195 let pos = Point { x, y };
196 let previously_hovered = self.hovered_nodes.clone();
197 for old_id in previously_hovered {
198 if !new_ids.contains(&old_id)
199 && let Some(target) = self.renderer.scene().find_target(old_id)
200 {
201 let exit_event = self
202 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
203 .with_buttons(self.buttons_pressed)
204 .with_source(self.pointer_source);
205 self.dispatch_targets(std::iter::once(target), exit_event, false);
206 }
207 }
208
209 for hit in &hits {
210 if !self.hovered_nodes.contains(&hit.node_id()) {
211 let enter_event = self
212 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
213 .with_buttons(self.buttons_pressed)
214 .with_source(self.pointer_source);
215 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
216 }
217 }
218
219 self.hovered_nodes = new_ids;
220 self.apply_hovered_pointer_icon(&hits);
221
222 if !hits.is_empty() {
223 let event = self
224 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
225 .with_buttons(self.buttons_pressed)
226 .with_source(self.pointer_source);
227 self.dispatch_targets(hits, event, true);
228 true
229 } else {
230 false
231 }
232 }
233
234 pub fn pointer_pressed(&mut self) -> bool {
235 self.pointer_pressed_at_time(None)
236 }
237
238 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
241 let event_time = self.realtime_pointer_event_time(time_ms);
242 self.pointer_pressed_at_event_time(event_time)
243 }
244
245 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
247 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
248 return true;
249 }
250 let _event_handler = enter_event_handler_scope();
251 let app_context = Rc::clone(&self.app_context);
252 let result = app_context.enter(|| {
253 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
254 });
255 if result {
256 self.mark_dirty();
257 }
258 log::trace!(
259 target: "cranpose::input",
260 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
261 event_time.platform_time_ms,
262 event_time.animation_time_nanos,
263 );
264 result
265 }
266
267 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
268 self.buttons_pressed.insert(PointerButton::Primary);
269
270 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
271 if hits.is_empty() {
272 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
273 false
274 } else {
275 let event = self
276 .pointer_event(
277 PointerEventKind::Down,
278 Point {
279 x: self.cursor.0,
280 y: self.cursor.1,
281 },
282 Point {
283 x: self.cursor.0,
284 y: self.cursor.1,
285 },
286 event_time,
287 )
288 .with_buttons(self.buttons_pressed)
289 .with_source(self.pointer_source);
290
291 let mut delivered_capture_paths = Vec::new();
292 let mut applier = self.composition.applier_mut();
293 for hit in hits {
294 let node_id = hit.node_id();
295 delivered_capture_paths.push(hit.capture_path());
296 hit.dispatch_with_applier(&mut applier, event.clone());
297 log::trace!(
298 target: "cranpose::input",
299 "dispatch {:?} node={} consumed={} stop_on_consume=true",
300 event.kind,
301 node_id,
302 event.is_consumed(),
303 );
304 if event.is_consumed() {
305 break;
306 }
307 }
308
309 self.hit_path_tracker
310 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
311 log::trace!(
312 target: "cranpose::input",
313 "pointer_pressed_inner cached_hit_path={:?}",
314 self.hit_path_tracker.get_path(PointerId::PRIMARY),
315 );
316
317 true
318 }
319 }
320
321 pub fn pointer_released(&mut self) -> bool {
322 self.pointer_released_at_time(None)
323 }
324
325 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
338 self.pointer_released_at_position_time(x, y, None)
339 }
340
341 pub fn pointer_released_at_position_time(
345 &mut self,
346 x: f32,
347 y: f32,
348 time_ms: Option<i64>,
349 ) -> bool {
350 let event_time = self.realtime_pointer_event_time(time_ms);
351 self.pointer_released_at_position_event_time(x, y, event_time)
352 }
353
354 pub fn pointer_released_at_position_event_time(
356 &mut self,
357 x: f32,
358 y: f32,
359 event_time: PointerEventTime,
360 ) -> bool {
361 let _event_handler = enter_event_handler_scope();
362 let app_context = Rc::clone(&self.app_context);
363 let result = app_context.enter(|| {
364 run_in_mutable_snapshot(|| {
365 self.cursor = (x, y);
366 self.pointer_released_inner(event_time)
367 })
368 .unwrap_or(false)
369 });
370 if result {
371 self.mark_dirty();
372 }
373 log::trace!(
374 target: "cranpose::input",
375 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
376 event_time.platform_time_ms,
377 event_time.animation_time_nanos,
378 );
379 result
380 }
381
382 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
385 let event_time = self.realtime_pointer_event_time(time_ms);
386 self.pointer_released_at_event_time(event_time)
387 }
388
389 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
391 let _event_handler = enter_event_handler_scope();
392 let app_context = Rc::clone(&self.app_context);
393 let result = app_context.enter(|| {
394 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
395 });
396 if result {
397 self.mark_dirty();
398 }
399 log::trace!(
400 target: "cranpose::input",
401 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
402 event_time.platform_time_ms,
403 event_time.animation_time_nanos,
404 );
405 result
406 }
407
408 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
409 self.buttons_pressed.remove(PointerButton::Primary);
410 let corrected_buttons = self.buttons_pressed;
411 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
412
413 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
414
415 if !targets.is_empty() {
416 let event = self
417 .pointer_event(
418 PointerEventKind::Up,
419 Point {
420 x: self.cursor.0,
421 y: self.cursor.1,
422 },
423 Point {
424 x: self.cursor.0,
425 y: self.cursor.1,
426 },
427 event_time,
428 )
429 .with_buttons(corrected_buttons)
430 .with_source(self.pointer_source);
431
432 self.dispatch_targets(targets, event, false);
433 true
434 } else {
435 false
436 }
437 }
438
439 pub fn secondary_pointer_pressed(
448 &mut self,
449 pointer_id: u64,
450 x: f32,
451 y: f32,
452 time_ms: Option<i64>,
453 ) -> bool {
454 let event_time = self.realtime_pointer_event_time(time_ms);
455 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
456 }
457
458 pub fn secondary_pointer_moved(
460 &mut self,
461 pointer_id: u64,
462 x: f32,
463 y: f32,
464 time_ms: Option<i64>,
465 ) -> bool {
466 let event_time = self.realtime_pointer_event_time(time_ms);
467 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
468 }
469
470 pub fn secondary_pointer_released(
472 &mut self,
473 pointer_id: u64,
474 x: f32,
475 y: f32,
476 time_ms: Option<i64>,
477 ) -> bool {
478 let event_time = self.realtime_pointer_event_time(time_ms);
479 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
480 }
481
482 fn dispatch_secondary_pointer(
483 &mut self,
484 kind: PointerEventKind,
485 pointer_id: u64,
486 x: f32,
487 y: f32,
488 event_time: PointerEventTime,
489 ) -> bool {
490 if pointer_id == 0 {
491 log::warn!(
492 target: "cranpose::input",
493 "secondary pointer dispatch called with the primary pointer id"
494 );
495 return false;
496 }
497
498 let _event_handler = enter_event_handler_scope();
499 let app_context = Rc::clone(&self.app_context);
500 let result = app_context.enter(|| {
501 run_in_mutable_snapshot(|| {
502 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
503 return false;
504 }
505 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
506 if targets.is_empty() {
507 return false;
508 }
509 let pos = Point { x, y };
510 let event = self
511 .pointer_event(kind, pos, pos, event_time)
512 .with_buttons(self.buttons_pressed)
513 .with_id(pointer_id)
514 .with_source(self.pointer_source);
515 self.dispatch_targets(targets, event, false);
516 true
517 })
518 .unwrap_or(false)
519 });
520 if result {
521 self.mark_dirty();
522 }
523 log::trace!(
524 target: "cranpose::input",
525 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
526 event_time.platform_time_ms,
527 event_time.animation_time_nanos,
528 );
529 result
530 }
531
532 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
538 let event_time = self.realtime_pointer_event_time(None);
539 let _event_handler = enter_event_handler_scope();
540 let app_context = Rc::clone(&self.app_context);
541 let result = app_context.enter(|| {
542 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
543 .unwrap_or(false)
544 });
545 if result {
546 self.mark_dirty();
547 }
548 log::trace!(
549 target: "cranpose::input",
550 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
551 );
552 result
553 }
554
555 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
556 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
557 return false;
558 }
559
560 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
561 if hits.is_empty() {
562 return false;
563 }
564
565 let pos = Point {
566 x: self.cursor.0,
567 y: self.cursor.1,
568 };
569 let event = self
570 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
571 .with_buttons(self.buttons_pressed)
572 .with_zoom_delta(zoom_factor)
573 .with_source(self.pointer_source);
574
575 self.dispatch_to_hits(&hits, event)
576 }
577
578 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
603 if wheel.is_zoom() {
604 let zoom_factor = wheel.zoom_factor();
605 log::trace!(
606 target: "cranpose::input",
607 "wheel zoom factor={zoom_factor:.4}"
608 );
609 return self.pointer_zoomed(zoom_factor);
610 }
611
612 let rotary =
613 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
614 if self.rotary_scrolled(rotary) {
615 return true;
616 }
617
618 let delta = wheel.scroll_delta();
619 log::trace!(
620 target: "cranpose::input",
621 "wheel delta ({:.2},{:.2}) alt={}",
622 delta.x,
623 delta.y,
624 wheel.modifiers.alt
625 );
626 self.pointer_scrolled(delta.x, delta.y)
627 }
628
629 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
637 let event_time = self.realtime_pointer_event_time(None);
638 let _event_handler = enter_event_handler_scope();
639 let app_context = Rc::clone(&self.app_context);
640 let result = app_context.enter(|| {
641 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
642 .unwrap_or(false)
643 });
644 if result {
645 self.mark_dirty();
646 }
647 log::trace!(
648 target: "cranpose::input",
649 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
650 );
651 result
652 }
653
654 fn pointer_scrolled_inner(
655 &mut self,
656 delta_x: f32,
657 delta_y: f32,
658 event_time: PointerEventTime,
659 ) -> bool {
660 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
661 return false;
662 }
663
664 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
665 if hits.is_empty() {
666 return false;
667 }
668
669 let event = self
670 .pointer_event(
671 PointerEventKind::Scroll,
672 Point {
673 x: self.cursor.0,
674 y: self.cursor.1,
675 },
676 Point {
677 x: self.cursor.0,
678 y: self.cursor.1,
679 },
680 event_time,
681 )
682 .with_buttons(self.buttons_pressed)
683 .with_scroll_delta(Point {
684 x: delta_x,
685 y: delta_y,
686 })
687 .with_source(self.pointer_source);
688
689 self.dispatch_to_hits(&hits, event)
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 cranpose_ui::pointer_icon_session::set_pointer_icon(PointerIcon::DEFAULT);
910 }
911
912 pub fn refresh_pointer_icon(&self) {
921 let app_context = Rc::clone(&self.app_context);
922 app_context.enter(cranpose_ui::pointer_icon_session::refresh_pointer_icon);
923 }
924
925 pub fn take_pointer_icon_change(&self) -> Option<PointerIcon> {
926 let app_context = Rc::clone(&self.app_context);
927 app_context.enter(cranpose_ui::pointer_icon_session::take_pointer_icon_change)
928 }
929
930 fn apply_hovered_pointer_icon(
936 &self,
937 hits: &[<<R as Renderer>::Scene as RenderScene>::HitTarget],
938 ) {
939 let icon = hits
940 .iter()
941 .find_map(HitTestTarget::pointer_icon)
942 .unwrap_or(PointerIcon::DEFAULT);
943 cranpose_ui::pointer_icon_session::set_pointer_icon(icon);
944 }
945
946 pub fn set_platform_text_input(
953 &mut self,
954 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
955 ) {
956 let app_context = Rc::clone(&self.app_context);
957 app_context
958 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
959 }
960
961 pub fn notify_app_paused(&mut self) {
968 let app_context = Rc::clone(&self.app_context);
969 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
970 }
971
972 pub fn notify_app_resumed(&mut self) -> bool {
980 let app_context = Rc::clone(&self.app_context);
981 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
982 }
983
984 fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
993 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Tab {
994 return false;
995 }
996 if event.modifiers.ctrl || event.modifiers.meta || event.modifiers.alt {
997 return false;
998 }
999 let direction = if event.modifiers.shift {
1000 FocusDirection::Previous
1001 } else {
1002 FocusDirection::Next
1003 };
1004 self.move_focus_in_context(direction)
1005 }
1006
1007 fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
1011 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
1012 return false;
1013 }
1014 self.dismiss_top_modal_in_context()
1015 }
1016
1017 pub fn dismiss_top_modal(&mut self) -> bool {
1020 let _event_handler = enter_event_handler_scope();
1021 let app_context = Rc::clone(&self.app_context);
1022 app_context.enter(|| self.dismiss_top_modal_in_context())
1023 }
1024
1025 fn dismiss_top_modal_in_context(&mut self) -> bool {
1026 let closed = run_in_mutable_snapshot(|| {
1027 if cranpose_ui::modal_depth() > 0 {
1028 cranpose_ui::dispatch_modal_back()
1029 } else {
1030 cranpose_ui::dismiss_top_popup()
1031 }
1032 })
1033 .unwrap_or(false);
1034 if closed {
1035 self.mark_dirty();
1036 }
1037 closed
1038 }
1039
1040 pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
1043 let order = self.with_layout_tree(|layout_tree| {
1044 layout_tree
1045 .map(cranpose_ui::collect_focus_order)
1046 .unwrap_or_default()
1047 });
1048 cranpose_ui::set_focus_order(order);
1049 let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
1050 .unwrap_or(false);
1051 if moved {
1052 self.mark_dirty();
1053 }
1054 moved
1055 }
1056
1057 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1058 let _event_handler = enter_event_handler_scope();
1059 let app_context = Rc::clone(&self.app_context);
1060 app_context.enter(|| self.on_key_event_inner(event))
1061 }
1062
1063 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1064 use KeyEventType::KeyDown;
1065
1066 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1067 #[cfg(all(
1068 feature = "clipboard-native",
1069 not(target_arch = "wasm32"),
1070 not(target_os = "android"),
1071 not(target_os = "ios")
1072 ))]
1073 {
1074 match event.key_code {
1075 KeyCode::C => {
1076 if let Some(text) = self.on_copy_inner() {
1077 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1078 return true;
1079 }
1080 }
1081 KeyCode::X => {
1082 if let Some(text) = self.on_cut_inner() {
1083 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1084 self.mark_dirty();
1085 self.request_layout_pass();
1086 return true;
1087 }
1088 }
1089 KeyCode::V => {
1090 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1091 && self.on_paste_inner(&text)
1092 {
1093 return true;
1094 }
1095 }
1096 _ => {}
1097 }
1098 }
1099 }
1100
1101 if self.on_focus_key(event) {
1102 return true;
1103 }
1104
1105 if self.on_escape_key(event) {
1106 return true;
1107 }
1108
1109 if !cranpose_ui::text_field_focus::has_focused_field() {
1110 return false;
1111 }
1112
1113 let handled =
1114 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1115 .unwrap_or(false);
1116
1117 if handled {
1118 self.mark_dirty();
1119 self.request_layout_pass();
1120 }
1121
1122 handled
1123 }
1124
1125 pub fn on_paste(&mut self, text: &str) -> bool {
1129 let _event_handler = enter_event_handler_scope();
1130 let app_context = Rc::clone(&self.app_context);
1131 app_context.enter(|| self.on_paste_inner(text))
1132 }
1133
1134 fn on_paste_inner(&mut self, text: &str) -> bool {
1135 let handled =
1136 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1137 .unwrap_or(false);
1138
1139 if handled {
1140 self.mark_dirty();
1141 self.request_layout_pass();
1142 }
1143
1144 handled
1145 }
1146
1147 pub fn on_copy(&mut self) -> Option<String> {
1151 let app_context = Rc::clone(&self.app_context);
1152 app_context.enter(|| self.on_copy_inner())
1153 }
1154
1155 fn on_copy_inner(&mut self) -> Option<String> {
1156 cranpose_ui::text_field_focus::dispatch_copy()
1157 }
1158
1159 pub fn on_cut(&mut self) -> Option<String> {
1163 let _event_handler = enter_event_handler_scope();
1164 let app_context = Rc::clone(&self.app_context);
1165 app_context.enter(|| self.on_cut_inner())
1166 }
1167
1168 fn on_cut_inner(&mut self) -> Option<String> {
1169 let text =
1170 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1171
1172 if text.is_some() {
1173 self.mark_dirty();
1174 self.request_layout_pass();
1175 }
1176
1177 text
1178 }
1179
1180 #[cfg(all(
1184 feature = "clipboard-native",
1185 target_os = "linux",
1186 not(target_arch = "wasm32")
1187 ))]
1188 pub fn set_primary_selection(&mut self, text: &str) {
1189 use arboard::{LinuxClipboardKind, SetExtLinux};
1190 if let Some(ref mut clipboard) = self.clipboard {
1191 let result = clipboard
1192 .set()
1193 .clipboard(LinuxClipboardKind::Primary)
1194 .text(text.to_string());
1195 if let Err(e) = result {
1196 log::debug!("Primary selection set failed: {:?}", e);
1197 }
1198 }
1199 }
1200
1201 #[cfg(not(all(
1202 feature = "clipboard-native",
1203 target_os = "linux",
1204 not(target_arch = "wasm32")
1205 )))]
1206 pub fn set_primary_selection(&mut self, _text: &str) {}
1207
1208 #[cfg(all(
1211 feature = "clipboard-native",
1212 target_os = "linux",
1213 not(target_arch = "wasm32")
1214 ))]
1215 pub fn get_primary_selection(&mut self) -> Option<String> {
1216 use arboard::{GetExtLinux, LinuxClipboardKind};
1217 if let Some(ref mut clipboard) = self.clipboard {
1218 clipboard
1219 .get()
1220 .clipboard(LinuxClipboardKind::Primary)
1221 .text()
1222 .ok()
1223 } else {
1224 None
1225 }
1226 }
1227
1228 #[cfg(not(all(
1229 feature = "clipboard-native",
1230 target_os = "linux",
1231 not(target_arch = "wasm32")
1232 )))]
1233 pub fn get_primary_selection(&mut self) -> Option<String> {
1234 None
1235 }
1236
1237 pub fn sync_selection_to_primary(&mut self) {
1240 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1241 {
1242 if let Some(text) = self.on_copy() {
1243 self.set_primary_selection(&text);
1244 }
1245 }
1246 }
1247
1248 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1256 let _event_handler = enter_event_handler_scope();
1257 let app_context = Rc::clone(&self.app_context);
1258 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1259 }
1260
1261 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1262 let handled = run_in_mutable_snapshot(|| {
1263 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1264 })
1265 .unwrap_or(false);
1266
1267 if handled {
1268 self.mark_dirty();
1269 self.request_layout_pass();
1270 }
1271
1272 handled
1273 }
1274
1275 pub fn on_ime_finish_composing(&mut self) -> bool {
1279 let _event_handler = enter_event_handler_scope();
1280 let app_context = Rc::clone(&self.app_context);
1281 app_context.enter(|| self.on_ime_finish_composing_inner())
1282 }
1283
1284 fn on_ime_finish_composing_inner(&mut self) -> bool {
1285 let handled =
1286 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1287 .unwrap_or(false);
1288
1289 if handled {
1290 self.mark_dirty();
1291 self.request_layout_pass();
1292 }
1293
1294 handled
1295 }
1296
1297 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1301 let _event_handler = enter_event_handler_scope();
1302 let app_context = Rc::clone(&self.app_context);
1303 app_context.enter(|| {
1304 let handled = run_in_mutable_snapshot(|| {
1305 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1306 start_bytes,
1307 end_bytes,
1308 )
1309 })
1310 .unwrap_or(false);
1311
1312 if handled {
1313 self.mark_dirty();
1314 self.request_layout_pass();
1315 }
1316
1317 handled
1318 })
1319 }
1320
1321 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1326 let _event_handler = enter_event_handler_scope();
1327 let app_context = Rc::clone(&self.app_context);
1328 app_context.enter(|| {
1329 let handled = run_in_mutable_snapshot(|| {
1330 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1331 })
1332 .unwrap_or(false);
1333
1334 if handled {
1335 self.mark_dirty();
1336 }
1337
1338 handled
1339 })
1340 }
1341
1342 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1346 let app_context = Rc::clone(&self.app_context);
1347 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1348 }
1349
1350 pub fn ime_caret_geometry(
1354 &mut self,
1355 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1356 let app_context = Rc::clone(&self.app_context);
1357 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1358 }
1359
1360 pub fn clear_text_field_focus(&mut self) {
1363 let _event_handler = enter_event_handler_scope();
1364 let app_context = Rc::clone(&self.app_context);
1365 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1366 self.mark_dirty();
1367 self.request_layout_pass();
1368 }
1369
1370 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1373 let _event_handler = enter_event_handler_scope();
1374 let app_context = Rc::clone(&self.app_context);
1375 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1376 }
1377
1378 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1379 let handled = run_in_mutable_snapshot(|| {
1380 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1381 })
1382 .unwrap_or(false);
1383
1384 if handled {
1385 self.mark_dirty();
1386 self.request_layout_pass();
1387 }
1388
1389 handled
1390 }
1391}