1use super::*;
2
3impl<R> AppShell<R>
4where
5 R: Renderer,
6 R::Error: Debug,
7{
8 fn resolve_gesture_targets(
9 &self,
10 pointer: PointerId,
11 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
12 self.resolve_hit_path(pointer)
13 }
14
15 fn resolve_hit_path(
22 &self,
23 pointer: PointerId,
24 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
25 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
26 return Vec::new();
27 };
28
29 let scene = self.renderer.scene();
30 let targets: Vec<_> = node_ids
31 .iter()
32 .filter_map(|&id| scene.find_target(id))
33 .collect();
34 log::trace!(
35 target: "cranpose::input",
36 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
37 targets.len()
38 );
39 targets
40 }
41
42 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
43 where
44 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
45 {
46 let mut applier = self.composition.applier_mut();
47 for target in targets {
48 let node_id = target.node_id();
49 target.dispatch_with_applier(&mut applier, event.clone());
50 log::trace!(
51 target: "cranpose::input",
52 "dispatch {:?} node={} consumed={} stop_on_consume={}",
53 event.kind,
54 node_id,
55 event.is_consumed(),
56 stop_on_consume,
57 );
58 if stop_on_consume && event.is_consumed() {
59 break;
60 }
61 }
62 }
63
64 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
65 self.set_cursor_at_time(x, y, None)
66 }
67
68 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
75 let _event_handler = enter_event_handler_scope();
76 let app_context = Rc::clone(&self.app_context);
77 let result = app_context.enter(|| {
78 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, time_ms)).unwrap_or(false)
79 });
80 if result {
81 self.mark_dirty();
82 }
83 log::trace!(
84 target: "cranpose::input",
85 "set_cursor ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
86 );
87 result
88 }
89
90 fn set_cursor_inner(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
91 self.cursor = (x, y);
92
93 if self.buttons_pressed != PointerButtons::NONE {
97 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
98 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
99 if !targets.is_empty() {
100 let event =
101 PointerEvent::new(PointerEventKind::Move, Point { x, y }, Point { x, y })
102 .with_buttons(self.buttons_pressed)
103 .with_time_ms(time_ms);
104 self.dispatch_targets(targets, event, false);
105 return true;
106 }
107
108 return false;
109 }
110
111 return false;
114 }
115
116 let hits = self.renderer.scene().hit_test(x, y);
119 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
120
121 let pos = Point { x, y };
123 let previously_hovered = self.hovered_nodes.clone();
124 for old_id in previously_hovered {
125 if !new_ids.contains(&old_id) {
126 if let Some(target) = self.renderer.scene().find_target(old_id) {
127 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos)
128 .with_buttons(self.buttons_pressed);
129 self.dispatch_targets(std::iter::once(target), exit_event, false);
130 }
131 }
132 }
133
134 for hit in &hits {
136 if !self.hovered_nodes.contains(&hit.node_id()) {
137 let enter_event = PointerEvent::new(PointerEventKind::Enter, pos, pos)
138 .with_buttons(self.buttons_pressed);
139 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
140 }
141 }
142
143 self.hovered_nodes = new_ids;
144
145 if !hits.is_empty() {
146 let event = PointerEvent::new(PointerEventKind::Move, pos, pos)
147 .with_buttons(self.buttons_pressed)
148 .with_time_ms(time_ms);
149 self.dispatch_targets(hits, event, true);
150 true
151 } else {
152 false
153 }
154 }
155
156 pub fn pointer_pressed(&mut self) -> bool {
157 self.pointer_pressed_at_time(None)
158 }
159
160 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
163 let _event_handler = enter_event_handler_scope();
164 let app_context = Rc::clone(&self.app_context);
165 let result = app_context.enter(|| {
166 run_in_mutable_snapshot(|| self.pointer_pressed_inner(time_ms)).unwrap_or(false)
167 });
168 if result {
169 self.mark_dirty();
170 }
171 log::trace!(target: "cranpose::input", "pointer_pressed time_ms={time_ms:?} -> {result}");
172 result
173 }
174
175 fn pointer_pressed_inner(&mut self, time_ms: Option<i64>) -> bool {
176 self.buttons_pressed.insert(PointerButton::Primary);
178
179 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
187 if hits.is_empty() {
188 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
189 false
190 } else {
191 let event = PointerEvent::new(
192 PointerEventKind::Down,
193 Point {
194 x: self.cursor.0,
195 y: self.cursor.1,
196 },
197 Point {
198 x: self.cursor.0,
199 y: self.cursor.1,
200 },
201 )
202 .with_buttons(self.buttons_pressed)
203 .with_time_ms(time_ms);
204
205 let mut delivered_capture_paths = Vec::new();
206 let mut applier = self.composition.applier_mut();
207 for hit in hits {
208 let node_id = hit.node_id();
209 delivered_capture_paths.push(hit.capture_path());
210 hit.dispatch_with_applier(&mut applier, event.clone());
211 log::trace!(
212 target: "cranpose::input",
213 "dispatch {:?} node={} consumed={} stop_on_consume=true",
214 event.kind,
215 node_id,
216 event.is_consumed(),
217 );
218 if event.is_consumed() {
219 break;
220 }
221 }
222
223 self.hit_path_tracker
224 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
225 log::trace!(
226 target: "cranpose::input",
227 "pointer_pressed_inner cached_hit_path={:?}",
228 self.hit_path_tracker.get_path(PointerId::PRIMARY),
229 );
230
231 true
232 }
233 }
234
235 pub fn pointer_released(&mut self) -> bool {
236 self.pointer_released_at_time(None)
237 }
238
239 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
252 self.pointer_released_at_position_time(x, y, None)
253 }
254
255 pub fn pointer_released_at_position_time(
259 &mut self,
260 x: f32,
261 y: f32,
262 time_ms: Option<i64>,
263 ) -> bool {
264 let _event_handler = enter_event_handler_scope();
265 let app_context = Rc::clone(&self.app_context);
266 let result = app_context.enter(|| {
267 run_in_mutable_snapshot(|| {
268 self.cursor = (x, y);
269 self.pointer_released_inner(time_ms)
270 })
271 .unwrap_or(false)
272 });
273 if result {
274 self.mark_dirty();
275 }
276 log::trace!(
277 target: "cranpose::input",
278 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
279 );
280 result
281 }
282
283 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
286 let _event_handler = enter_event_handler_scope();
287 let app_context = Rc::clone(&self.app_context);
288 let result = app_context.enter(|| {
289 run_in_mutable_snapshot(|| self.pointer_released_inner(time_ms)).unwrap_or(false)
290 });
291 if result {
292 self.mark_dirty();
293 }
294 log::trace!(target: "cranpose::input", "pointer_released time_ms={time_ms:?} -> {result}");
295 result
296 }
297
298 fn pointer_released_inner(&mut self, time_ms: Option<i64>) -> bool {
299 self.buttons_pressed.remove(PointerButton::Primary);
302 let corrected_buttons = self.buttons_pressed;
303 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
304
305 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
307
308 if !targets.is_empty() {
309 let event = PointerEvent::new(
310 PointerEventKind::Up,
311 Point {
312 x: self.cursor.0,
313 y: self.cursor.1,
314 },
315 Point {
316 x: self.cursor.0,
317 y: self.cursor.1,
318 },
319 )
320 .with_buttons(corrected_buttons)
321 .with_time_ms(time_ms);
322
323 self.dispatch_targets(targets, event, false);
324 true
325 } else {
326 false
327 }
328 }
329
330 pub fn secondary_pointer_pressed(
339 &mut self,
340 pointer_id: u64,
341 x: f32,
342 y: f32,
343 time_ms: Option<i64>,
344 ) -> bool {
345 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, time_ms)
346 }
347
348 pub fn secondary_pointer_moved(
350 &mut self,
351 pointer_id: u64,
352 x: f32,
353 y: f32,
354 time_ms: Option<i64>,
355 ) -> bool {
356 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, time_ms)
357 }
358
359 pub fn secondary_pointer_released(
361 &mut self,
362 pointer_id: u64,
363 x: f32,
364 y: f32,
365 time_ms: Option<i64>,
366 ) -> bool {
367 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, time_ms)
368 }
369
370 fn dispatch_secondary_pointer(
371 &mut self,
372 kind: PointerEventKind,
373 pointer_id: u64,
374 x: f32,
375 y: f32,
376 time_ms: Option<i64>,
377 ) -> bool {
378 if pointer_id == 0 {
379 log::warn!(
380 target: "cranpose::input",
381 "secondary pointer dispatch called with the primary pointer id"
382 );
383 return false;
384 }
385
386 let _event_handler = enter_event_handler_scope();
387 let app_context = Rc::clone(&self.app_context);
388 let result = app_context.enter(|| {
389 run_in_mutable_snapshot(|| {
390 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
391 return false;
392 }
393 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
394 if targets.is_empty() {
395 return false;
396 }
397 let pos = Point { x, y };
398 let event = PointerEvent::new(kind, pos, pos)
399 .with_buttons(self.buttons_pressed)
400 .with_time_ms(time_ms)
401 .with_id(pointer_id);
402 self.dispatch_targets(targets, event, false);
403 true
404 })
405 .unwrap_or(false)
406 });
407 if result {
408 self.mark_dirty();
409 }
410 log::trace!(
411 target: "cranpose::input",
412 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
413 );
414 result
415 }
416
417 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
423 let _event_handler = enter_event_handler_scope();
424 let app_context = Rc::clone(&self.app_context);
425 let result = app_context.enter(|| {
426 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor)).unwrap_or(false)
427 });
428 if result {
429 self.mark_dirty();
430 }
431 log::trace!(
432 target: "cranpose::input",
433 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
434 );
435 result
436 }
437
438 fn pointer_zoomed_inner(&mut self, zoom_factor: f32) -> bool {
439 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
440 return false;
441 }
442
443 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
444 if hits.is_empty() {
445 return false;
446 }
447
448 let pos = Point {
449 x: self.cursor.0,
450 y: self.cursor.1,
451 };
452 let event = PointerEvent::new(PointerEventKind::Zoom, pos, pos)
453 .with_buttons(self.buttons_pressed)
454 .with_zoom_delta(zoom_factor);
455
456 let capture_paths = hits
457 .iter()
458 .map(|hit| hit.capture_path())
459 .collect::<Vec<_>>();
460 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
461 .into_iter()
462 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
463 .collect::<Vec<_>>();
464
465 self.dispatch_targets(targets, event.clone(), true);
466
467 event.is_consumed()
468 }
469
470 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
474 let _event_handler = enter_event_handler_scope();
475 let app_context = Rc::clone(&self.app_context);
476 let result = app_context.enter(|| {
477 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y))
478 .unwrap_or(false)
479 });
480 if result {
481 self.mark_dirty();
482 }
483 log::trace!(
484 target: "cranpose::input",
485 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
486 );
487 result
488 }
489
490 fn pointer_scrolled_inner(&mut self, delta_x: f32, delta_y: f32) -> bool {
491 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
492 return false;
493 }
494
495 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
496 if hits.is_empty() {
497 return false;
498 }
499
500 let event = PointerEvent::new(
501 PointerEventKind::Scroll,
502 Point {
503 x: self.cursor.0,
504 y: self.cursor.1,
505 },
506 Point {
507 x: self.cursor.0,
508 y: self.cursor.1,
509 },
510 )
511 .with_buttons(self.buttons_pressed)
512 .with_scroll_delta(Point {
513 x: delta_x,
514 y: delta_y,
515 });
516
517 let capture_paths = hits
518 .iter()
519 .map(|hit| hit.capture_path())
520 .collect::<Vec<_>>();
521 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
522 .into_iter()
523 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
524 .collect::<Vec<_>>();
525
526 self.dispatch_targets(targets, event.clone(), true);
527
528 event.is_consumed()
529 }
530
531 pub fn cancel_gesture(&mut self) {
537 let _event_handler = enter_event_handler_scope();
538 let app_context = Rc::clone(&self.app_context);
539 let _ = app_context.enter(|| {
540 run_in_mutable_snapshot(|| {
541 self.cancel_gesture_inner();
542 })
543 });
544 }
545
546 fn cancel_gesture_inner(&mut self) {
547 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
548
549 self.hit_path_tracker.clear();
551 self.buttons_pressed = PointerButtons::NONE;
552
553 if !targets.is_empty() {
554 let event = PointerEvent::new(
555 PointerEventKind::Cancel,
556 Point {
557 x: self.cursor.0,
558 y: self.cursor.1,
559 },
560 Point {
561 x: self.cursor.0,
562 y: self.cursor.1,
563 },
564 );
565
566 self.dispatch_targets(targets, event, false);
567 }
568
569 let pos = Point {
571 x: self.cursor.0,
572 y: self.cursor.1,
573 };
574 let hovered_nodes = self.hovered_nodes.clone();
575 for node_id in hovered_nodes {
576 if let Some(target) = self.renderer.scene().find_target(node_id) {
577 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos);
578 self.dispatch_targets(std::iter::once(target), exit_event, false);
579 }
580 }
581 self.hovered_nodes.clear();
582 }
583
584 pub fn set_platform_text_input(
591 &mut self,
592 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
593 ) {
594 let app_context = Rc::clone(&self.app_context);
595 app_context
596 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
597 }
598
599 pub fn clear_platform_text_input(&mut self) {
601 let app_context = Rc::clone(&self.app_context);
602 app_context.enter(cranpose_ui::text_input_session::clear_platform_text_input_handler);
603 }
604
605 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
612 let _event_handler = enter_event_handler_scope();
613 let app_context = Rc::clone(&self.app_context);
614 app_context.enter(|| self.on_key_event_inner(event))
615 }
616
617 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
619 use KeyEventType::KeyDown;
620
621 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
623 #[cfg(all(
625 feature = "clipboard-native",
626 not(target_arch = "wasm32"),
627 not(target_os = "android"),
628 not(target_os = "ios")
629 ))]
630 {
631 match event.key_code {
632 KeyCode::C => {
634 let text = self.on_copy_inner();
636 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
637 let _ = clipboard.set_text(&text);
638 return true;
639 }
640 }
641 KeyCode::X => {
643 let text = self.on_cut_inner();
645 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
646 let _ = clipboard.set_text(&text);
647 self.mark_dirty();
648 self.request_layout_pass();
649 return true;
650 }
651 }
652 KeyCode::V => {
654 let text = self.clipboard.as_mut().and_then(|cb| cb.get_text().ok());
656 if let Some(text) = text {
657 if self.on_paste_inner(&text) {
658 return true;
659 }
660 }
661 }
662 _ => {}
663 }
664 }
665 }
666
667 if !cranpose_ui::text_field_focus::has_focused_field() {
669 return false;
670 }
671
672 let handled = run_in_mutable_snapshot(|| {
676 cranpose_ui::text_field_focus::dispatch_key_event(event)
679 })
680 .unwrap_or(false);
681
682 if handled {
683 self.mark_dirty();
685 self.request_layout_pass();
686 }
687
688 handled
689 }
690
691 pub fn on_paste(&mut self, text: &str) -> bool {
695 let _event_handler = enter_event_handler_scope();
696 let app_context = Rc::clone(&self.app_context);
697 app_context.enter(|| self.on_paste_inner(text))
698 }
699
700 fn on_paste_inner(&mut self, text: &str) -> bool {
701 let handled =
705 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
706 .unwrap_or(false);
707
708 if handled {
709 self.mark_dirty();
710 self.request_layout_pass();
711 }
712
713 handled
714 }
715
716 pub fn on_copy(&mut self) -> Option<String> {
720 let app_context = Rc::clone(&self.app_context);
721 app_context.enter(|| self.on_copy_inner())
722 }
723
724 fn on_copy_inner(&mut self) -> Option<String> {
725 cranpose_ui::text_field_focus::dispatch_copy()
727 }
728
729 pub fn on_cut(&mut self) -> Option<String> {
733 let _event_handler = enter_event_handler_scope();
734 let app_context = Rc::clone(&self.app_context);
735 app_context.enter(|| self.on_cut_inner())
736 }
737
738 fn on_cut_inner(&mut self) -> Option<String> {
739 let text =
740 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
741
742 if text.is_some() {
743 self.mark_dirty();
744 self.request_layout_pass();
745 }
746
747 text
748 }
749
750 #[cfg(all(
754 feature = "clipboard-native",
755 target_os = "linux",
756 not(target_arch = "wasm32")
757 ))]
758 pub fn set_primary_selection(&mut self, text: &str) {
759 use arboard::{LinuxClipboardKind, SetExtLinux};
760 if let Some(ref mut clipboard) = self.clipboard {
761 let result = clipboard
762 .set()
763 .clipboard(LinuxClipboardKind::Primary)
764 .text(text.to_string());
765 if let Err(e) = result {
766 log::debug!("Primary selection set failed: {:?}", e);
768 }
769 }
770 }
771
772 #[cfg(not(all(
773 feature = "clipboard-native",
774 target_os = "linux",
775 not(target_arch = "wasm32")
776 )))]
777 pub fn set_primary_selection(&mut self, _text: &str) {}
778
779 #[cfg(all(
782 feature = "clipboard-native",
783 target_os = "linux",
784 not(target_arch = "wasm32")
785 ))]
786 pub fn get_primary_selection(&mut self) -> Option<String> {
787 use arboard::{GetExtLinux, LinuxClipboardKind};
788 if let Some(ref mut clipboard) = self.clipboard {
789 clipboard
790 .get()
791 .clipboard(LinuxClipboardKind::Primary)
792 .text()
793 .ok()
794 } else {
795 None
796 }
797 }
798
799 #[cfg(not(all(
800 feature = "clipboard-native",
801 target_os = "linux",
802 not(target_arch = "wasm32")
803 )))]
804 pub fn get_primary_selection(&mut self) -> Option<String> {
805 None
806 }
807
808 pub fn sync_selection_to_primary(&mut self) {
811 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
812 {
813 if let Some(text) = self.on_copy() {
814 self.set_primary_selection(&text);
815 }
816 }
817 }
818
819 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
827 let _event_handler = enter_event_handler_scope();
828 let app_context = Rc::clone(&self.app_context);
829 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
830 }
831
832 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
833 let handled = run_in_mutable_snapshot(|| {
835 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
836 })
837 .unwrap_or(false);
838
839 if handled {
840 self.mark_dirty();
841 self.request_layout_pass();
843 }
844
845 handled
846 }
847
848 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
851 let _event_handler = enter_event_handler_scope();
852 let app_context = Rc::clone(&self.app_context);
853 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
854 }
855
856 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
857 let handled = run_in_mutable_snapshot(|| {
858 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
859 })
860 .unwrap_or(false);
861
862 if handled {
863 self.mark_dirty();
864 self.request_layout_pass();
865 }
866
867 handled
868 }
869}