1use super::*;
2
3impl<R> AppShell<R>
4where
5 R: Renderer,
6 R::Error: Debug,
7{
8 fn pointer_event(
9 &self,
10 kind: PointerEventKind,
11 position: Point,
12 global_position: Point,
13 event_time: PointerEventTime,
14 ) -> PointerEvent {
15 PointerEvent::new(kind, position, global_position)
16 .with_time_ms(event_time.platform_time_ms)
17 .with_animation_time_nanos(event_time.animation_time_nanos)
18 }
19
20 fn resolve_gesture_targets(
21 &self,
22 pointer: PointerId,
23 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
24 self.resolve_hit_path(pointer)
25 }
26
27 fn resolve_hit_path(
34 &self,
35 pointer: PointerId,
36 ) -> Vec<<<R as Renderer>::Scene as RenderScene>::HitTarget> {
37 let Some(node_ids) = self.hit_path_tracker.dispatch_order(pointer) else {
38 return Vec::new();
39 };
40
41 let scene = self.renderer.scene();
42 let targets: Vec<_> = node_ids
43 .iter()
44 .filter_map(|&id| scene.find_target(id))
45 .collect();
46 log::trace!(
47 target: "cranpose::input",
48 "resolve_hit_path pointer={pointer:?} cached={node_ids:?} resolved_count={}",
49 targets.len()
50 );
51 targets
52 }
53
54 fn dispatch_targets<I>(&mut self, targets: I, event: PointerEvent, stop_on_consume: bool)
55 where
56 I: IntoIterator<Item = <<R as Renderer>::Scene as RenderScene>::HitTarget>,
57 {
58 let mut applier = self.composition.applier_mut();
59 for target in targets {
60 let node_id = target.node_id();
61 target.dispatch_with_applier(&mut applier, event.clone());
62 log::trace!(
63 target: "cranpose::input",
64 "dispatch {:?} node={} consumed={} stop_on_consume={}",
65 event.kind,
66 node_id,
67 event.is_consumed(),
68 stop_on_consume,
69 );
70 if stop_on_consume && event.is_consumed() {
71 break;
72 }
73 }
74 }
75
76 pub fn set_pointer_source(&mut self, source: PointerSource) {
82 self.pointer_source = source;
83 }
84
85 pub fn pointer_source(&self) -> PointerSource {
87 self.pointer_source
88 }
89
90 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
91 self.set_cursor_at_time(x, y, None)
92 }
93
94 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
101 let event_time = self.realtime_pointer_event_time(time_ms);
102 self.set_cursor_at_event_time(x, y, event_time)
103 }
104
105 pub fn set_cursor_at_event_time(
107 &mut self,
108 x: f32,
109 y: f32,
110 event_time: PointerEventTime,
111 ) -> bool {
112 let _event_handler = enter_event_handler_scope();
113 let app_context = Rc::clone(&self.app_context);
114 let result = app_context.enter(|| {
115 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, event_time)).unwrap_or(false)
116 });
117 if result {
118 self.mark_dirty();
119 }
120 log::trace!(
121 target: "cranpose::input",
122 "set_cursor ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
123 event_time.platform_time_ms,
124 event_time.animation_time_nanos,
125 );
126 result
127 }
128
129 fn set_cursor_inner(&mut self, x: f32, y: f32, event_time: PointerEventTime) -> bool {
130 self.cursor = (x, y);
131
132 if self.buttons_pressed != PointerButtons::NONE {
136 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
137 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
138 if !targets.is_empty() {
139 let event = self
140 .pointer_event(
141 PointerEventKind::Move,
142 Point { x, y },
143 Point { x, y },
144 event_time,
145 )
146 .with_buttons(self.buttons_pressed)
147 .with_source(self.pointer_source);
148 self.dispatch_targets(targets, event, false);
149 return true;
150 }
151
152 return false;
153 }
154
155 return false;
158 }
159
160 let hits = self.renderer.scene().hit_test(x, y);
163 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
164
165 let pos = Point { x, y };
167 let previously_hovered = self.hovered_nodes.clone();
168 for old_id in previously_hovered {
169 if !new_ids.contains(&old_id) {
170 if let Some(target) = self.renderer.scene().find_target(old_id) {
171 let exit_event = self
172 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
173 .with_buttons(self.buttons_pressed)
174 .with_source(self.pointer_source);
175 self.dispatch_targets(std::iter::once(target), exit_event, false);
176 }
177 }
178 }
179
180 for hit in &hits {
182 if !self.hovered_nodes.contains(&hit.node_id()) {
183 let enter_event = self
184 .pointer_event(PointerEventKind::Enter, pos, pos, event_time)
185 .with_buttons(self.buttons_pressed)
186 .with_source(self.pointer_source);
187 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
188 }
189 }
190
191 self.hovered_nodes = new_ids;
192
193 if !hits.is_empty() {
194 let event = self
195 .pointer_event(PointerEventKind::Move, pos, pos, event_time)
196 .with_buttons(self.buttons_pressed)
197 .with_source(self.pointer_source);
198 self.dispatch_targets(hits, event, true);
199 true
200 } else {
201 false
202 }
203 }
204
205 pub fn pointer_pressed(&mut self) -> bool {
206 self.pointer_pressed_at_time(None)
207 }
208
209 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
212 let event_time = self.realtime_pointer_event_time(time_ms);
213 self.pointer_pressed_at_event_time(event_time)
214 }
215
216 pub fn pointer_pressed_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
218 let _event_handler = enter_event_handler_scope();
219 let app_context = Rc::clone(&self.app_context);
220 let result = app_context.enter(|| {
221 run_in_mutable_snapshot(|| self.pointer_pressed_inner(event_time)).unwrap_or(false)
222 });
223 if result {
224 self.mark_dirty();
225 }
226 log::trace!(
227 target: "cranpose::input",
228 "pointer_pressed time_ms={:?} animation_time_nanos={} -> {result}",
229 event_time.platform_time_ms,
230 event_time.animation_time_nanos,
231 );
232 result
233 }
234
235 fn pointer_pressed_inner(&mut self, event_time: PointerEventTime) -> bool {
236 self.buttons_pressed.insert(PointerButton::Primary);
238
239 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
247 if hits.is_empty() {
248 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
249 false
250 } else {
251 let event = self
252 .pointer_event(
253 PointerEventKind::Down,
254 Point {
255 x: self.cursor.0,
256 y: self.cursor.1,
257 },
258 Point {
259 x: self.cursor.0,
260 y: self.cursor.1,
261 },
262 event_time,
263 )
264 .with_buttons(self.buttons_pressed)
265 .with_source(self.pointer_source);
266
267 let mut delivered_capture_paths = Vec::new();
268 let mut applier = self.composition.applier_mut();
269 for hit in hits {
270 let node_id = hit.node_id();
271 delivered_capture_paths.push(hit.capture_path());
272 hit.dispatch_with_applier(&mut applier, event.clone());
273 log::trace!(
274 target: "cranpose::input",
275 "dispatch {:?} node={} consumed={} stop_on_consume=true",
276 event.kind,
277 node_id,
278 event.is_consumed(),
279 );
280 if event.is_consumed() {
281 break;
282 }
283 }
284
285 self.hit_path_tracker
286 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
287 log::trace!(
288 target: "cranpose::input",
289 "pointer_pressed_inner cached_hit_path={:?}",
290 self.hit_path_tracker.get_path(PointerId::PRIMARY),
291 );
292
293 true
294 }
295 }
296
297 pub fn pointer_released(&mut self) -> bool {
298 self.pointer_released_at_time(None)
299 }
300
301 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
314 self.pointer_released_at_position_time(x, y, None)
315 }
316
317 pub fn pointer_released_at_position_time(
321 &mut self,
322 x: f32,
323 y: f32,
324 time_ms: Option<i64>,
325 ) -> bool {
326 let event_time = self.realtime_pointer_event_time(time_ms);
327 self.pointer_released_at_position_event_time(x, y, event_time)
328 }
329
330 pub fn pointer_released_at_position_event_time(
332 &mut self,
333 x: f32,
334 y: f32,
335 event_time: PointerEventTime,
336 ) -> bool {
337 let _event_handler = enter_event_handler_scope();
338 let app_context = Rc::clone(&self.app_context);
339 let result = app_context.enter(|| {
340 run_in_mutable_snapshot(|| {
341 self.cursor = (x, y);
342 self.pointer_released_inner(event_time)
343 })
344 .unwrap_or(false)
345 });
346 if result {
347 self.mark_dirty();
348 }
349 log::trace!(
350 target: "cranpose::input",
351 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
352 event_time.platform_time_ms,
353 event_time.animation_time_nanos,
354 );
355 result
356 }
357
358 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
361 let event_time = self.realtime_pointer_event_time(time_ms);
362 self.pointer_released_at_event_time(event_time)
363 }
364
365 pub fn pointer_released_at_event_time(&mut self, event_time: PointerEventTime) -> bool {
367 let _event_handler = enter_event_handler_scope();
368 let app_context = Rc::clone(&self.app_context);
369 let result = app_context.enter(|| {
370 run_in_mutable_snapshot(|| self.pointer_released_inner(event_time)).unwrap_or(false)
371 });
372 if result {
373 self.mark_dirty();
374 }
375 log::trace!(
376 target: "cranpose::input",
377 "pointer_released time_ms={:?} animation_time_nanos={} -> {result}",
378 event_time.platform_time_ms,
379 event_time.animation_time_nanos,
380 );
381 result
382 }
383
384 fn pointer_released_inner(&mut self, event_time: PointerEventTime) -> bool {
385 self.buttons_pressed.remove(PointerButton::Primary);
388 let corrected_buttons = self.buttons_pressed;
389 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
390
391 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
393
394 if !targets.is_empty() {
395 let event = self
396 .pointer_event(
397 PointerEventKind::Up,
398 Point {
399 x: self.cursor.0,
400 y: self.cursor.1,
401 },
402 Point {
403 x: self.cursor.0,
404 y: self.cursor.1,
405 },
406 event_time,
407 )
408 .with_buttons(corrected_buttons)
409 .with_source(self.pointer_source);
410
411 self.dispatch_targets(targets, event, false);
412 true
413 } else {
414 false
415 }
416 }
417
418 pub fn secondary_pointer_pressed(
427 &mut self,
428 pointer_id: u64,
429 x: f32,
430 y: f32,
431 time_ms: Option<i64>,
432 ) -> bool {
433 let event_time = self.realtime_pointer_event_time(time_ms);
434 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, event_time)
435 }
436
437 pub fn secondary_pointer_moved(
439 &mut self,
440 pointer_id: u64,
441 x: f32,
442 y: f32,
443 time_ms: Option<i64>,
444 ) -> bool {
445 let event_time = self.realtime_pointer_event_time(time_ms);
446 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, event_time)
447 }
448
449 pub fn secondary_pointer_released(
451 &mut self,
452 pointer_id: u64,
453 x: f32,
454 y: f32,
455 time_ms: Option<i64>,
456 ) -> bool {
457 let event_time = self.realtime_pointer_event_time(time_ms);
458 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, event_time)
459 }
460
461 fn dispatch_secondary_pointer(
462 &mut self,
463 kind: PointerEventKind,
464 pointer_id: u64,
465 x: f32,
466 y: f32,
467 event_time: PointerEventTime,
468 ) -> bool {
469 if pointer_id == 0 {
470 log::warn!(
471 target: "cranpose::input",
472 "secondary pointer dispatch called with the primary pointer id"
473 );
474 return false;
475 }
476
477 let _event_handler = enter_event_handler_scope();
478 let app_context = Rc::clone(&self.app_context);
479 let result = app_context.enter(|| {
480 run_in_mutable_snapshot(|| {
481 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
482 return false;
483 }
484 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
485 if targets.is_empty() {
486 return false;
487 }
488 let pos = Point { x, y };
489 let event = self
490 .pointer_event(kind, pos, pos, event_time)
491 .with_buttons(self.buttons_pressed)
492 .with_id(pointer_id)
493 .with_source(self.pointer_source);
494 self.dispatch_targets(targets, event, false);
495 true
496 })
497 .unwrap_or(false)
498 });
499 if result {
500 self.mark_dirty();
501 }
502 log::trace!(
503 target: "cranpose::input",
504 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={:?} animation_time_nanos={} -> {result}",
505 event_time.platform_time_ms,
506 event_time.animation_time_nanos,
507 );
508 result
509 }
510
511 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
517 let event_time = self.realtime_pointer_event_time(None);
518 let _event_handler = enter_event_handler_scope();
519 let app_context = Rc::clone(&self.app_context);
520 let result = app_context.enter(|| {
521 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor, event_time))
522 .unwrap_or(false)
523 });
524 if result {
525 self.mark_dirty();
526 }
527 log::trace!(
528 target: "cranpose::input",
529 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
530 );
531 result
532 }
533
534 fn pointer_zoomed_inner(&mut self, zoom_factor: f32, event_time: PointerEventTime) -> bool {
535 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
536 return false;
537 }
538
539 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
540 if hits.is_empty() {
541 return false;
542 }
543
544 let pos = Point {
545 x: self.cursor.0,
546 y: self.cursor.1,
547 };
548 let event = self
549 .pointer_event(PointerEventKind::Zoom, pos, pos, event_time)
550 .with_buttons(self.buttons_pressed)
551 .with_zoom_delta(zoom_factor)
552 .with_source(self.pointer_source);
553
554 let capture_paths = hits
555 .iter()
556 .map(|hit| hit.capture_path())
557 .collect::<Vec<_>>();
558 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
559 .into_iter()
560 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
561 .collect::<Vec<_>>();
562
563 self.dispatch_targets(targets, event.clone(), true);
564
565 event.is_consumed()
566 }
567
568 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
572 let event_time = self.realtime_pointer_event_time(None);
573 let _event_handler = enter_event_handler_scope();
574 let app_context = Rc::clone(&self.app_context);
575 let result = app_context.enter(|| {
576 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y, event_time))
577 .unwrap_or(false)
578 });
579 if result {
580 self.mark_dirty();
581 }
582 log::trace!(
583 target: "cranpose::input",
584 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
585 );
586 result
587 }
588
589 fn pointer_scrolled_inner(
590 &mut self,
591 delta_x: f32,
592 delta_y: f32,
593 event_time: PointerEventTime,
594 ) -> bool {
595 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
596 return false;
597 }
598
599 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
600 if hits.is_empty() {
601 return false;
602 }
603
604 let event = self
605 .pointer_event(
606 PointerEventKind::Scroll,
607 Point {
608 x: self.cursor.0,
609 y: self.cursor.1,
610 },
611 Point {
612 x: self.cursor.0,
613 y: self.cursor.1,
614 },
615 event_time,
616 )
617 .with_buttons(self.buttons_pressed)
618 .with_scroll_delta(Point {
619 x: delta_x,
620 y: delta_y,
621 })
622 .with_source(self.pointer_source);
623
624 let capture_paths = hits
625 .iter()
626 .map(|hit| hit.capture_path())
627 .collect::<Vec<_>>();
628 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
629 .into_iter()
630 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
631 .collect::<Vec<_>>();
632
633 self.dispatch_targets(targets, event.clone(), true);
634
635 event.is_consumed()
636 }
637
638 pub fn cancel_gesture(&mut self) {
644 let event_time = self.realtime_pointer_event_time(None);
645 let _event_handler = enter_event_handler_scope();
646 let app_context = Rc::clone(&self.app_context);
647 let _ = app_context.enter(|| {
648 run_in_mutable_snapshot(|| {
649 self.cancel_gesture_inner(event_time);
650 })
651 });
652 }
653
654 fn cancel_gesture_inner(&mut self, event_time: PointerEventTime) {
655 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
656
657 self.hit_path_tracker.clear();
659 self.buttons_pressed = PointerButtons::NONE;
660
661 if !targets.is_empty() {
662 let event = self
663 .pointer_event(
664 PointerEventKind::Cancel,
665 Point {
666 x: self.cursor.0,
667 y: self.cursor.1,
668 },
669 Point {
670 x: self.cursor.0,
671 y: self.cursor.1,
672 },
673 event_time,
674 )
675 .with_source(self.pointer_source);
676
677 self.dispatch_targets(targets, event, false);
678 }
679
680 let pos = Point {
682 x: self.cursor.0,
683 y: self.cursor.1,
684 };
685 let hovered_nodes = self.hovered_nodes.clone();
686 for node_id in hovered_nodes {
687 if let Some(target) = self.renderer.scene().find_target(node_id) {
688 let exit_event = self
689 .pointer_event(PointerEventKind::Exit, pos, pos, event_time)
690 .with_source(self.pointer_source);
691 self.dispatch_targets(std::iter::once(target), exit_event, false);
692 }
693 }
694 self.hovered_nodes.clear();
695 }
696
697 pub fn set_platform_text_input(
704 &mut self,
705 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
706 ) {
707 let app_context = Rc::clone(&self.app_context);
708 app_context
709 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
710 }
711
712 pub fn clear_platform_text_input(&mut self) {
714 let app_context = Rc::clone(&self.app_context);
715 app_context.enter(cranpose_ui::text_input_session::clear_platform_text_input_handler);
716 }
717
718 pub fn notify_app_paused(&mut self) {
725 let app_context = Rc::clone(&self.app_context);
726 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
727 }
728
729 pub fn notify_app_resumed(&mut self) -> bool {
737 let app_context = Rc::clone(&self.app_context);
738 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
739 }
740
741 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
748 let _event_handler = enter_event_handler_scope();
749 let app_context = Rc::clone(&self.app_context);
750 app_context.enter(|| self.on_key_event_inner(event))
751 }
752
753 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
755 use KeyEventType::KeyDown;
756
757 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
759 #[cfg(all(
760 feature = "clipboard-native",
761 not(target_arch = "wasm32"),
762 not(target_os = "android"),
763 not(target_os = "ios")
764 ))]
765 {
766 match event.key_code {
767 KeyCode::C => {
769 if let Some(text) = self.on_copy_inner() {
770 cranpose_ui::clipboard_session::clipboard_write_text(&text);
771 return true;
772 }
773 }
774 KeyCode::X => {
776 if let Some(text) = self.on_cut_inner() {
777 cranpose_ui::clipboard_session::clipboard_write_text(&text);
778 self.mark_dirty();
779 self.request_layout_pass();
780 return true;
781 }
782 }
783 KeyCode::V => {
785 if let Some(text) = cranpose_ui::clipboard_session::clipboard_read_text() {
786 if self.on_paste_inner(&text) {
787 return true;
788 }
789 }
790 }
791 _ => {}
792 }
793 }
794 }
795
796 if !cranpose_ui::text_field_focus::has_focused_field() {
798 return false;
799 }
800
801 let handled = run_in_mutable_snapshot(|| {
805 cranpose_ui::text_field_focus::dispatch_key_event(event)
808 })
809 .unwrap_or(false);
810
811 if handled {
812 self.mark_dirty();
814 self.request_layout_pass();
815 }
816
817 handled
818 }
819
820 pub fn on_paste(&mut self, text: &str) -> bool {
824 let _event_handler = enter_event_handler_scope();
825 let app_context = Rc::clone(&self.app_context);
826 app_context.enter(|| self.on_paste_inner(text))
827 }
828
829 fn on_paste_inner(&mut self, text: &str) -> bool {
830 let handled =
834 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
835 .unwrap_or(false);
836
837 if handled {
838 self.mark_dirty();
839 self.request_layout_pass();
840 }
841
842 handled
843 }
844
845 pub fn on_copy(&mut self) -> Option<String> {
849 let app_context = Rc::clone(&self.app_context);
850 app_context.enter(|| self.on_copy_inner())
851 }
852
853 fn on_copy_inner(&mut self) -> Option<String> {
854 cranpose_ui::text_field_focus::dispatch_copy()
856 }
857
858 pub fn on_cut(&mut self) -> Option<String> {
862 let _event_handler = enter_event_handler_scope();
863 let app_context = Rc::clone(&self.app_context);
864 app_context.enter(|| self.on_cut_inner())
865 }
866
867 fn on_cut_inner(&mut self) -> Option<String> {
868 let text =
869 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
870
871 if text.is_some() {
872 self.mark_dirty();
873 self.request_layout_pass();
874 }
875
876 text
877 }
878
879 #[cfg(all(
883 feature = "clipboard-native",
884 target_os = "linux",
885 not(target_arch = "wasm32")
886 ))]
887 pub fn set_primary_selection(&mut self, text: &str) {
888 use arboard::{LinuxClipboardKind, SetExtLinux};
889 if let Some(ref mut clipboard) = self.clipboard {
890 let result = clipboard
891 .set()
892 .clipboard(LinuxClipboardKind::Primary)
893 .text(text.to_string());
894 if let Err(e) = result {
895 log::debug!("Primary selection set failed: {:?}", e);
897 }
898 }
899 }
900
901 #[cfg(not(all(
902 feature = "clipboard-native",
903 target_os = "linux",
904 not(target_arch = "wasm32")
905 )))]
906 pub fn set_primary_selection(&mut self, _text: &str) {}
907
908 #[cfg(all(
911 feature = "clipboard-native",
912 target_os = "linux",
913 not(target_arch = "wasm32")
914 ))]
915 pub fn get_primary_selection(&mut self) -> Option<String> {
916 use arboard::{GetExtLinux, LinuxClipboardKind};
917 if let Some(ref mut clipboard) = self.clipboard {
918 clipboard
919 .get()
920 .clipboard(LinuxClipboardKind::Primary)
921 .text()
922 .ok()
923 } else {
924 None
925 }
926 }
927
928 #[cfg(not(all(
929 feature = "clipboard-native",
930 target_os = "linux",
931 not(target_arch = "wasm32")
932 )))]
933 pub fn get_primary_selection(&mut self) -> Option<String> {
934 None
935 }
936
937 pub fn sync_selection_to_primary(&mut self) {
940 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
941 {
942 if let Some(text) = self.on_copy() {
943 self.set_primary_selection(&text);
944 }
945 }
946 }
947
948 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
956 let _event_handler = enter_event_handler_scope();
957 let app_context = Rc::clone(&self.app_context);
958 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
959 }
960
961 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
962 let handled = run_in_mutable_snapshot(|| {
964 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
965 })
966 .unwrap_or(false);
967
968 if handled {
969 self.mark_dirty();
970 self.request_layout_pass();
972 }
973
974 handled
975 }
976
977 pub fn on_ime_finish_composing(&mut self) -> bool {
981 let _event_handler = enter_event_handler_scope();
982 let app_context = Rc::clone(&self.app_context);
983 app_context.enter(|| self.on_ime_finish_composing_inner())
984 }
985
986 fn on_ime_finish_composing_inner(&mut self) -> bool {
987 let handled =
988 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
989 .unwrap_or(false);
990
991 if handled {
992 self.mark_dirty();
993 self.request_layout_pass();
994 }
995
996 handled
997 }
998
999 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1003 let _event_handler = enter_event_handler_scope();
1004 let app_context = Rc::clone(&self.app_context);
1005 app_context.enter(|| {
1006 let handled = run_in_mutable_snapshot(|| {
1007 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
1008 start_bytes,
1009 end_bytes,
1010 )
1011 })
1012 .unwrap_or(false);
1013
1014 if handled {
1015 self.mark_dirty();
1016 self.request_layout_pass();
1017 }
1018
1019 handled
1020 })
1021 }
1022
1023 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
1028 let _event_handler = enter_event_handler_scope();
1029 let app_context = Rc::clone(&self.app_context);
1030 app_context.enter(|| {
1031 let handled = run_in_mutable_snapshot(|| {
1032 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
1033 })
1034 .unwrap_or(false);
1035
1036 if handled {
1039 self.mark_dirty();
1040 }
1041
1042 handled
1043 })
1044 }
1045
1046 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
1050 let app_context = Rc::clone(&self.app_context);
1051 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
1052 }
1053
1054 pub fn ime_caret_geometry(
1058 &mut self,
1059 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
1060 let app_context = Rc::clone(&self.app_context);
1061 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
1062 }
1063
1064 pub fn clear_text_field_focus(&mut self) {
1067 let _event_handler = enter_event_handler_scope();
1068 let app_context = Rc::clone(&self.app_context);
1069 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
1070 self.mark_dirty();
1071 self.request_layout_pass();
1072 }
1073
1074 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1077 let _event_handler = enter_event_handler_scope();
1078 let app_context = Rc::clone(&self.app_context);
1079 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
1080 }
1081
1082 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1083 let handled = run_in_mutable_snapshot(|| {
1084 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1085 })
1086 .unwrap_or(false);
1087
1088 if handled {
1089 self.mark_dirty();
1090 self.request_layout_pass();
1091 }
1092
1093 handled
1094 }
1095}