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
221 if !hits.is_empty() {
222 let event = self
223 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
224 .with_buttons(self.buttons_pressed)
225 .with_source(self.pointer_source);
226 self.dispatch_targets(hits, event, true);
227 true
228 } else {
229 false
230 }
231 }
232
233 pub fn pointer_pressed(&mut self) -> bool {
234 self.pointer_pressed_at_time(None)
235 }
236
237 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
240 let event_time = self.realtime_pointer_event_time(time_ms);
241 self.pointer_pressed_at_event_time(event_time)
242 }
243
244 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
246 if self.dev_overlay_press(self.cursor.0, self.cursor.1) {
247 return true;
248 }
249 let _event_handler = enter_event_handler_scope();
250 let app_context = Rc::clone(&self.app_context);
251 let result = app_context.enter(|| {
252 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
253 });
254 if result {
255 self.mark_dirty();
256 }
257 log::trace!(
258 target: "cranpose::input",
259 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
260 event_time.platform_time_ms,
261 event_time.animation_time_nanos,
262 );
263 result
264 }
265
266 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
267 self.buttons_pressed.insert(PointerButton::Primary);
268
269 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
270 if hits.is_empty() {
271 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
272 false
273 } else {
274 let event = self
275 .pointer_event(
276 PointerEventKind::Down,
277 Point {
278 x: self.cursor.0,
279 y: self.cursor.1,
280 },
281 Point {
282 x: self.cursor.0,
283 y: self.cursor.1,
284 },
285 event_time,
286 )
287 .with_buttons(self.buttons_pressed)
288 .with_source(self.pointer_source);
289
290 let mut delivered_capture_paths = Vec::new();
291 let mut applier = self.composition.applier_mut();
292 for hit in hits {
293 let node_id = hit.node_id();
294 delivered_capture_paths.push(hit.capture_path());
295 hit.dispatch_with_applier(&mut applier, event.clone());
296 log::trace!(
297 target: "cranpose::input",
298 "dispatch {:?} node={} consumed={} stop_on_consume=true",
299 event.kind,
300 node_id,
301 event.is_consumed(),
302 );
303 if event.is_consumed() {
304 break;
305 }
306 }
307
308 self.hit_path_tracker
309 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
310 log::trace!(
311 target: "cranpose::input",
312 "pointer_pressed_inner cached_hit_path={:?}",
313 self.hit_path_tracker.get_path(PointerId::PRIMARY),
314 );
315
316 true
317 }
318 }
319
320 pub fn pointer_released(&mut self) -> bool {
321 self.pointer_released_at_time(None)
322 }
323
324 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
337 self.pointer_released_at_position_time(x, y, None)
338 }
339
340 pub fn pointer_released_at_position_time(
344 &mut self,
345 x: f32,
346 y: f32,
347 time_ms: Option<i64>,
348 ) -> bool {
349 let event_time = self.realtime_pointer_event_time(time_ms);
350 self.pointer_released_at_position_event_time(x, y, event_time)
351 }
352
353 pub fn pointer_released_at_position_event_time(
355 &mut self,
356 x: f32,
357 y: f32,
358 event_time: PointerEventTime,
359 ) -> bool {
360 let _event_handler = enter_event_handler_scope();
361 let app_context = Rc::clone(&self.app_context);
362 let result = app_context.enter(|| {
363 run_in_mutable_snapshot(|| {
364 self.cursor = (x, y);
365 self.pointer_released_inner(event_time)
366 })
367 .unwrap_or(false)
368 });
369 if result {
370 self.mark_dirty();
371 }
372 log::trace!(
373 target: "cranpose::input",
374 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
375 event_time.platform_time_ms,
376 event_time.animation_time_nanos,
377 );
378 result
379 }
380
381 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
384 let event_time = self.realtime_pointer_event_time(time_ms);
385 self.pointer_released_at_event_time(event_time)
386 }
387
388 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
390 let _event_handler = enter_event_handler_scope();
391 let app_context = Rc::clone(&self.app_context);
392 let result = app_context.enter(|| {
393 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
394 });
395 if result {
396 self.mark_dirty();
397 }
398 log::trace!(
399 target: "cranpose::input",
400 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
401 event_time.platform_time_ms,
402 event_time.animation_time_nanos,
403 );
404 result
405 }
406
407 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
408 self.buttons_pressed.remove(PointerButton::Primary);
409 let corrected_buttons = self.buttons_pressed;
410 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
411
412 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
413
414 if !targets.is_empty() {
415 let event = self
416 .pointer_event(
417 PointerEventKind::Up,
418 Point {
419 x: self.cursor.0,
420 y: self.cursor.1,
421 },
422 Point {
423 x: self.cursor.0,
424 y: self.cursor.1,
425 },
426 event_time,
427 )
428 .with_buttons(corrected_buttons)
429 .with_source(self.pointer_source);
430
431 self.dispatch_targets(targets, event, false);
432 true
433 } else {
434 false
435 }
436 }
437
438 pub fn secondary_pointer_pressed(
447 &mut self,
448 pointer_id: u64,
449 x: f32,
450 y: f32,
451 time_ms: Option<i64>,
452 ) -> bool {
453 let event_time = self.realtime_pointer_event_time(time_ms);
454 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
455 }
456
457 pub fn secondary_pointer_moved(
459 &mut self,
460 pointer_id: u64,
461 x: f32,
462 y: f32,
463 time_ms: Option<i64>,
464 ) -> bool {
465 let event_time = self.realtime_pointer_event_time(time_ms);
466 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
467 }
468
469 pub fn secondary_pointer_released(
471 &mut self,
472 pointer_id: u64,
473 x: f32,
474 y: f32,
475 time_ms: Option<i64>,
476 ) -> bool {
477 let event_time = self.realtime_pointer_event_time(time_ms);
478 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
479 }
480
481 fn dispatch_secondary_pointer(
482 &mut self,
483 kind: PointerEventKind,
484 pointer_id: u64,
485 x: f32,
486 y: f32,
487 event_time: PointerEventTime,
488 ) -> bool {
489 if pointer_id == 0 {
490 log::warn!(
491 target: "cranpose::input",
492 "secondary pointer dispatch called with the primary pointer id"
493 );
494 return false;
495 }
496
497 let _event_handler = enter_event_handler_scope();
498 let app_context = Rc::clone(&self.app_context);
499 let result = app_context.enter(|| {
500 run_in_mutable_snapshot(|| {
501 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
502 return false;
503 }
504 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
505 if targets.is_empty() {
506 return false;
507 }
508 let pos = Point { x, y };
509 let event = self
510 .pointer_event(kind, pos, pos, event_time)
511 .with_buttons(self.buttons_pressed)
512 .with_id(pointer_id)
513 .with_source(self.pointer_source);
514 self.dispatch_targets(targets, event, false);
515 true
516 })
517 .unwrap_or(false)
518 });
519 if result {
520 self.mark_dirty();
521 }
522 log::trace!(
523 target: "cranpose::input",
524 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
525 event_time.platform_time_ms,
526 event_time.animation_time_nanos,
527 );
528 result
529 }
530
531 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
537 let event_time = self.realtime_pointer_event_time(None);
538 let _event_handler = enter_event_handler_scope();
539 let app_context = Rc::clone(&self.app_context);
540 let result = app_context.enter(|| {
541 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
542 .unwrap_or(false)
543 });
544 if result {
545 self.mark_dirty();
546 }
547 log::trace!(
548 target: "cranpose::input",
549 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
550 );
551 result
552 }
553
554 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
555 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
556 return false;
557 }
558
559 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
560 if hits.is_empty() {
561 return false;
562 }
563
564 let pos = Point {
565 x: self.cursor.0,
566 y: self.cursor.1,
567 };
568 let event = self
569 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
570 .with_buttons(self.buttons_pressed)
571 .with_zoom_delta(zoom_factor)
572 .with_source(self.pointer_source);
573
574 self.dispatch_to_hits(&hits, event)
575 }
576
577 pub fn wheel_scrolled(&mut self, wheel: crate::WheelScroll) -> bool {
602 if wheel.is_zoom() {
603 let zoom_factor = wheel.zoom_factor();
604 log::trace!(
605 target: "cranpose::input",
606 "wheel zoom factor={zoom_factor:.4}"
607 );
608 return self.pointer_zoomed(zoom_factor);
609 }
610
611 let rotary =
612 RotaryScrollEvent::from_wheel_pixels(wheel.delta.y, wheel.delta.x, wheel.uptime_millis);
613 if self.rotary_scrolled(rotary) {
614 return true;
615 }
616
617 let delta = wheel.scroll_delta();
618 log::trace!(
619 target: "cranpose::input",
620 "wheel delta ({:.2},{:.2}) alt={}",
621 delta.x,
622 delta.y,
623 wheel.modifiers.alt
624 );
625 self.pointer_scrolled(delta.x, delta.y)
626 }
627
628 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
636 let event_time = self.realtime_pointer_event_time(None);
637 let _event_handler = enter_event_handler_scope();
638 let app_context = Rc::clone(&self.app_context);
639 let result = app_context.enter(|| {
640 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
641 .unwrap_or(false)
642 });
643 if result {
644 self.mark_dirty();
645 }
646 log::trace!(
647 target: "cranpose::input",
648 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
649 );
650 result
651 }
652
653 fn pointer_scrolled_inner(
654 &mut self,
655 delta_x: f32,
656 delta_y: f32,
657 event_time: PointerEventTime,
658 ) -> bool {
659 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
660 return false;
661 }
662
663 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
664 if hits.is_empty() {
665 return false;
666 }
667
668 let event = self
669 .pointer_event(
670 PointerEventKind::Scroll,
671 Point {
672 x: self.cursor.0,
673 y: self.cursor.1,
674 },
675 Point {
676 x: self.cursor.0,
677 y: self.cursor.1,
678 },
679 event_time,
680 )
681 .with_buttons(self.buttons_pressed)
682 .with_scroll_delta(Point {
683 x: delta_x,
684 y: delta_y,
685 })
686 .with_source(self.pointer_source);
687
688 self.dispatch_to_hits(&hits, event)
689 }
690
691 pub fn set_on_rotary_scroll<F>(&mut self, handler: F)
702 where
703 F: Fn(RotaryScrollEvent) -> bool + 'static,
704 {
705 self.on_rotary_scroll = Some(Rc::new(handler));
706 }
707
708 pub fn clear_on_rotary_scroll(&mut self) {
710 self.on_rotary_scroll = None;
711 }
712
713 pub fn rotary_scroll_factor(&self) -> f32 {
716 self.rotary_scroll_factor
717 }
718
719 pub fn set_rotary_scroll_factor(&mut self, factor: f32) {
728 if factor.is_finite() && factor > 0.0 {
729 self.rotary_scroll_factor = factor;
730 }
731 }
732
733 pub fn rotary_scrolled_by_detents(&mut self, detents: f32, uptime_millis: u64) -> bool {
739 let factor = self.rotary_scroll_factor;
740 self.rotary_scrolled(RotaryScrollEvent::from_detents(
741 detents,
742 factor,
743 factor,
744 uptime_millis,
745 ))
746 }
747
748 pub fn rotary_scrolled(&mut self, event: RotaryScrollEvent) -> bool {
769 let _event_handler = enter_event_handler_scope();
770 let app_context = Rc::clone(&self.app_context);
771 let result = app_context.enter(|| {
772 run_in_mutable_snapshot(|| self.rotary_scrolled_inner(event)).unwrap_or(false)
773 });
774 if result {
775 self.mark_dirty();
776 }
777 log::trace!(
778 target: "cranpose::input",
779 "rotary_scrolled v={:.2} h={:.2} uptime={} -> {result}",
780 event.vertical_scroll_pixels,
781 event.horizontal_scroll_pixels,
782 event.uptime_millis,
783 );
784 result
785 }
786
787 fn rotary_scrolled_inner(&mut self, rotary: RotaryScrollEvent) -> bool {
788 if rotary.is_empty() {
789 return false;
790 }
791
792 let bubble_order = self.rotary_dispatch_order();
793 let position = Point {
794 x: self.cursor.0,
795 y: self.cursor.1,
796 };
797
798 if !bubble_order.is_empty() {
799 let capture_targets = bubble_order
800 .iter()
801 .rev()
802 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
803 .collect::<Vec<_>>();
804 let mut capture_event =
805 PointerEvent::rotary(PointerEventKind::RotaryScrollPre, rotary, position);
806 capture_event.modifiers = self.modifiers;
807 self.dispatch_targets(capture_targets, capture_event.clone(), true);
808 if capture_event.is_consumed() {
809 return true;
810 }
811
812 let bubble_targets = bubble_order
813 .iter()
814 .filter_map(|&node_id| self.renderer.scene().find_target(node_id))
815 .collect::<Vec<_>>();
816 let mut bubble_event =
817 PointerEvent::rotary(PointerEventKind::RotaryScroll, rotary, position);
818 bubble_event.modifiers = self.modifiers;
819 self.dispatch_targets(bubble_targets, bubble_event.clone(), true);
820 if bubble_event.is_consumed() {
821 return true;
822 }
823 }
824
825 if let Some(handler) = self.on_rotary_scroll.clone() {
826 return handler(rotary);
827 }
828
829 false
830 }
831
832 fn rotary_dispatch_order(&self) -> Vec<NodeId> {
833 if let Some(focused) = cranpose_ui::active_focus_target()
834 && let Some(target) = self.renderer.scene().find_target(focused)
835 {
836 let path = target.capture_path();
837 if !path.is_empty() {
838 return crate::hit_path_tracker::dispatch_order_for_paths(&[path]);
839 }
840 }
841
842 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
843 if hits.is_empty() {
844 return Vec::new();
845 }
846 let capture_paths = hits
847 .iter()
848 .map(|hit| hit.capture_path())
849 .collect::<Vec<_>>();
850 crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
851 }
852
853 pub fn cancel_gesture(&mut self) {
859 let event_time = self.realtime_pointer_event_time(None);
860 let _event_handler = enter_event_handler_scope();
861 let app_context = Rc::clone(&self.app_context);
862 let _ = app_context.enter(|| {
863 run_in_mutable_snapshot(|| {
864 self.cancel_gesture_inner(event_time);
865 })
866 });
867 }
868
869 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
870 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
871
872 self.hit_path_tracker.clear();
873 self.buttons_pressed = PointerButtons::NONE;
874
875 if !targets.is_empty() {
876 let event = self
877 .pointer_event(
878 PointerEventKind::Cancel,
879 Point {
880 x: self.cursor.0,
881 y: self.cursor.1,
882 },
883 Point {
884 x: self.cursor.0,
885 y: self.cursor.1,
886 },
887 event_time,
888 )
889 .with_source(self.pointer_source);
890
891 self.dispatch_targets(targets, event, false);
892 }
893
894 let pos = Point {
895 x: self.cursor.0,
896 y: self.cursor.1,
897 };
898 let hovered_nodes = self.hovered_nodes.clone();
899 for node_id in hovered_nodes {
900 if let Some(target) = self.renderer.scene().find_target(node_id) {
901 let exit_event = self
902 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
903 .with_source(self.pointer_source);
904 self.dispatch_targets(std::iter::once(target), exit_event, false);
905 }
906 }
907 self.hovered_nodes.clear();
908 }
909
910 pub fn set_platform_text_input(
917 &mut self,
918 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
919 ) {
920 let app_context = Rc::clone(&self.app_context);
921 app_context
922 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
923 }
924
925 pub fn notify_app_paused(&mut self) {
932 let app_context = Rc::clone(&self.app_context);
933 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
934 }
935
936 pub fn notify_app_resumed(&mut self) -> bool {
944 let app_context = Rc::clone(&self.app_context);
945 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
946 }
947
948 fn on_focus_key(&mut self, event: &KeyEvent) -> bool {
957 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Tab {
958 return false;
959 }
960 if event.modifiers.ctrl || event.modifiers.meta || event.modifiers.alt {
961 return false;
962 }
963 let direction = if event.modifiers.shift {
964 FocusDirection::Previous
965 } else {
966 FocusDirection::Next
967 };
968 self.move_focus_in_context(direction)
969 }
970
971 fn on_escape_key(&mut self, event: &KeyEvent) -> bool {
975 if event.event_type != KeyEventType::KeyDown || event.key_code != KeyCode::Escape {
976 return false;
977 }
978 self.dismiss_top_modal_in_context()
979 }
980
981 pub fn dismiss_top_modal(&mut self) -> bool {
984 let _event_handler = enter_event_handler_scope();
985 let app_context = Rc::clone(&self.app_context);
986 app_context.enter(|| self.dismiss_top_modal_in_context())
987 }
988
989 fn dismiss_top_modal_in_context(&mut self) -> bool {
990 let closed = run_in_mutable_snapshot(|| {
991 if cranpose_ui::modal_depth() > 0 {
992 cranpose_ui::dispatch_modal_back()
993 } else {
994 cranpose_ui::dismiss_top_popup()
995 }
996 })
997 .unwrap_or(false);
998 if closed {
999 self.mark_dirty();
1000 }
1001 closed
1002 }
1003
1004 pub fn move_focus_in_context(&mut self, direction: FocusDirection) -> bool {
1007 let order = self.with_layout_tree(|layout_tree| {
1008 layout_tree
1009 .map(cranpose_ui::collect_focus_order)
1010 .unwrap_or_default()
1011 });
1012 cranpose_ui::set_focus_order(order);
1013 let moved = run_in_mutable_snapshot(|| cranpose_ui::FocusManager.move_focus(direction))
1014 .unwrap_or(false);
1015 if moved {
1016 self.mark_dirty();
1017 }
1018 moved
1019 }
1020
1021 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
1022 let _event_handler = enter_event_handler_scope();
1023 let app_context = Rc::clone(&self.app_context);
1024 app_context.enter(|| self.on_key_event_inner(event))
1025 }
1026
1027 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
1028 use KeyEventType::KeyDown;
1029
1030 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
1031 #[cfg(all(
1032 feature = "clipboard-native",
1033 not(target_arch = "wasm32"),
1034 not(target_os = "android"),
1035 not(target_os = "ios")
1036 ))]
1037 {
1038 match event.key_code {
1039 KeyCode::C => {
1040 if let Some(text) = self.on_copy_inner() {
1041 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1042 return true;
1043 }
1044 }
1045 KeyCode::X => {
1046 if let Some(text) = self.on_cut_inner() {
1047 cranpose_ui::clipboard_session::clipboard_write_text(&text);
1048 self.mark_dirty();
1049 self.request_layout_pass();
1050 return true;
1051 }
1052 }
1053 KeyCode::V => {
1054 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text()
1055 && self.on_paste_inner(&text)
1056 {
1057 return true;
1058 }
1059 }
1060 _ => {}
1061 }
1062 }
1063 }
1064
1065 if self.on_focus_key(event) {
1066 return true;
1067 }
1068
1069 if self.on_escape_key(event) {
1070 return true;
1071 }
1072
1073 if !cranpose_ui::text_field_focus::has_focused_field() {
1074 return false;
1075 }
1076
1077 let handled =
1078 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_key_event(event))
1079 .unwrap_or(false);
1080
1081 if handled {
1082 self.mark_dirty();
1083 self.request_layout_pass();
1084 }
1085
1086 handled
1087 }
1088
1089 pub fn on_paste(&mut self, text: &str) -> bool {
1093 let _event_handler = enter_event_handler_scope();
1094 let app_context = Rc::clone(&self.app_context);
1095 app_context.enter(|| self.on_paste_inner(text))
1096 }
1097
1098 fn on_paste_inner(&mut self, text: &str) -> bool {
1099 let handled =
1100 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
1101 .unwrap_or(false);
1102
1103 if handled {
1104 self.mark_dirty();
1105 self.request_layout_pass();
1106 }
1107
1108 handled
1109 }
1110
1111 pub fn on_copy(&mut self) -> Option<String> {
1115 let app_context = Rc::clone(&self.app_context);
1116 app_context.enter(|| self.on_copy_inner())
1117 }
1118
1119 fn on_copy_inner(&mut self) -> Option<String> {
1120 cranpose_ui::text_field_focus::dispatch_copy()
1121 }
1122
1123 pub fn on_cut(&mut self) -> Option<String> {
1127 let _event_handler = enter_event_handler_scope();
1128 let app_context = Rc::clone(&self.app_context);
1129 app_context.enter(|| self.on_cut_inner())
1130 }
1131
1132 fn on_cut_inner(&mut self) -> Option<String> {
1133 let text =
1134 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
1135
1136 if text.is_some() {
1137 self.mark_dirty();
1138 self.request_layout_pass();
1139 }
1140
1141 text
1142 }
1143
1144 #[cfg(all(
1148 feature = "clipboard-native",
1149 target_os = "linux",
1150 not(target_arch = "wasm32")
1151 ))]
1152 pub fn set_primary_selection(&mut self, text: &str) {
1153 use arboard::{LinuxClipboardKind, SetExtLinux};
1154 if let Some(ref mut clipboard) = self.clipboard {
1155 let result = clipboard
1156 .set()
1157 .clipboard(LinuxClipboardKind::Primary)
1158 .text(text.to_string());
1159 if let Err(e) = result {
1160 log::debug!("Primary selection set failed: {:?}", e);
1161 }
1162 }
1163 }
1164
1165 #[cfg(not(all(
1166 feature = "clipboard-native",
1167 target_os = "linux",
1168 not(target_arch = "wasm32")
1169 )))]
1170 pub fn set_primary_selection(&mut self, _text: &str) {}
1171
1172 #[cfg(all(
1175 feature = "clipboard-native",
1176 target_os = "linux",
1177 not(target_arch = "wasm32")
1178 ))]
1179 pub fn get_primary_selection(&mut self) -> Option<String> {
1180 use arboard::{GetExtLinux, LinuxClipboardKind};
1181 if let Some(ref mut clipboard) = self.clipboard {
1182 clipboard
1183 .get()
1184 .clipboard(LinuxClipboardKind::Primary)
1185 .text()
1186 .ok()
1187 } else {
1188 None
1189 }
1190 }
1191
1192 #[cfg(not(all(
1193 feature = "clipboard-native",
1194 target_os = "linux",
1195 not(target_arch = "wasm32")
1196 )))]
1197 pub fn get_primary_selection(&mut self) -> Option<String> {
1198 None
1199 }
1200
1201 pub fn sync_selection_to_primary(&mut self) {
1204 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
1205 {
1206 if let Some(text) = self.on_copy() {
1207 self.set_primary_selection(&text);
1208 }
1209 }
1210 }
1211
1212 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1220 let _event_handler = enter_event_handler_scope();
1221 let app_context = Rc::clone(&self.app_context);
1222 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
1223 }
1224
1225 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
1226 let handled = run_in_mutable_snapshot(|| {
1227 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
1228 })
1229 .unwrap_or(false);
1230
1231 if handled {
1232 self.mark_dirty();
1233 self.request_layout_pass();
1234 }
1235
1236 handled
1237 }
1238
1239 pub fn on_ime_finish_composing(&mut self) -> bool {
1243 let _event_handler = enter_event_handler_scope();
1244 let app_context = Rc::clone(&self.app_context);
1245 app_context.enter(|| self.on_ime_finish_composing_inner())
1246 }
1247
1248 fn on_ime_finish_composing_inner(&mut self) -> bool {
1249 let handled =
1250 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
1251 .unwrap_or(false);
1252
1253 if handled {
1254 self.mark_dirty();
1255 self.request_layout_pass();
1256 }
1257
1258 handled
1259 }
1260
1261 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1265 let _event_handler = enter_event_handler_scope();
1266 let app_context = Rc::clone(&self.app_context);
1267 app_context.enter(|| {
1268 let handled = run_in_mutable_snapshot(|| {
1269 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1270 start_bytes,
1271 end_bytes,
1272 )
1273 })
1274 .unwrap_or(false);
1275
1276 if handled {
1277 self.mark_dirty();
1278 self.request_layout_pass();
1279 }
1280
1281 handled
1282 })
1283 }
1284
1285 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1290 let _event_handler = enter_event_handler_scope();
1291 let app_context = Rc::clone(&self.app_context);
1292 app_context.enter(|| {
1293 let handled = run_in_mutable_snapshot(|| {
1294 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1295 })
1296 .unwrap_or(false);
1297
1298 if handled {
1299 self.mark_dirty();
1300 }
1301
1302 handled
1303 })
1304 }
1305
1306 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1310 let app_context = Rc::clone(&self.app_context);
1311 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1312 }
1313
1314 pub fn ime_caret_geometry(
1318 &mut self,
1319 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1320 let app_context = Rc::clone(&self.app_context);
1321 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1322 }
1323
1324 pub fn clear_text_field_focus(&mut self) {
1327 let _event_handler = enter_event_handler_scope();
1328 let app_context = Rc::clone(&self.app_context);
1329 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1330 self.mark_dirty();
1331 self.request_layout_pass();
1332 }
1333
1334 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1337 let _event_handler = enter_event_handler_scope();
1338 let app_context = Rc::clone(&self.app_context);
1339 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1340 }
1341
1342 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1343 let handled = run_in_mutable_snapshot(|| {
1344 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1345 })
1346 .unwrap_or(false);
1347
1348 if handled {
1349 self.mark_dirty();
1350 self.request_layout_pass();
1351 }
1352
1353 handled
1354 }
1355}