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_pointer_source(&mut self, source: PointerSource) {
70 self.pointer_source = source;
71 }
72
73 pub fn pointer_source(&self) -> PointerSource {
75 self.pointer_source
76 }
77
78 pub fn set_cursor(&mut self, x: f32, y: f32) -> bool {
79 self.set_cursor_at_time(x, y, None)
80 }
81
82 pub fn set_cursor_at_time(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
89 let _event_handler = enter_event_handler_scope();
90 let app_context = Rc::clone(&self.app_context);
91 let result = app_context.enter(|| {
92 run_in_mutable_snapshot(|| self.set_cursor_inner(x, y, time_ms)).unwrap_or(false)
93 });
94 if result {
95 self.mark_dirty();
96 }
97 log::trace!(
98 target: "cranpose::input",
99 "set_cursor ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
100 );
101 result
102 }
103
104 fn set_cursor_inner(&mut self, x: f32, y: f32, time_ms: Option<i64>) -> bool {
105 self.cursor = (x, y);
106
107 if self.buttons_pressed != PointerButtons::NONE {
111 if self.hit_path_tracker.has_path(PointerId::PRIMARY) {
112 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
113 if !targets.is_empty() {
114 let event =
115 PointerEvent::new(PointerEventKind::Move, Point { x, y }, Point { x, y })
116 .with_buttons(self.buttons_pressed)
117 .with_time_ms(time_ms)
118 .with_source(self.pointer_source);
119 self.dispatch_targets(targets, event, false);
120 return true;
121 }
122
123 return false;
124 }
125
126 return false;
129 }
130
131 let hits = self.renderer.scene().hit_test(x, y);
134 let new_ids: Vec<NodeId> = hits.iter().map(|h| h.node_id()).collect();
135
136 let pos = Point { x, y };
138 let previously_hovered = self.hovered_nodes.clone();
139 for old_id in previously_hovered {
140 if !new_ids.contains(&old_id) {
141 if let Some(target) = self.renderer.scene().find_target(old_id) {
142 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos)
143 .with_buttons(self.buttons_pressed)
144 .with_source(self.pointer_source);
145 self.dispatch_targets(std::iter::once(target), exit_event, false);
146 }
147 }
148 }
149
150 for hit in &hits {
152 if !self.hovered_nodes.contains(&hit.node_id()) {
153 let enter_event = PointerEvent::new(PointerEventKind::Enter, pos, pos)
154 .with_buttons(self.buttons_pressed)
155 .with_source(self.pointer_source);
156 self.dispatch_targets(std::iter::once(hit.clone()), enter_event, false);
157 }
158 }
159
160 self.hovered_nodes = new_ids;
161
162 if !hits.is_empty() {
163 let event = PointerEvent::new(PointerEventKind::Move, pos, pos)
164 .with_buttons(self.buttons_pressed)
165 .with_time_ms(time_ms)
166 .with_source(self.pointer_source);
167 self.dispatch_targets(hits, event, true);
168 true
169 } else {
170 false
171 }
172 }
173
174 pub fn pointer_pressed(&mut self) -> bool {
175 self.pointer_pressed_at_time(None)
176 }
177
178 pub fn pointer_pressed_at_time(&mut self, time_ms: Option<i64>) -> bool {
181 let _event_handler = enter_event_handler_scope();
182 let app_context = Rc::clone(&self.app_context);
183 let result = app_context.enter(|| {
184 run_in_mutable_snapshot(|| self.pointer_pressed_inner(time_ms)).unwrap_or(false)
185 });
186 if result {
187 self.mark_dirty();
188 }
189 log::trace!(target: "cranpose::input", "pointer_pressed time_ms={time_ms:?} -> {result}");
190 result
191 }
192
193 fn pointer_pressed_inner(&mut self, time_ms: Option<i64>) -> bool {
194 self.buttons_pressed.insert(PointerButton::Primary);
196
197 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
205 if hits.is_empty() {
206 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
207 false
208 } else {
209 let event = PointerEvent::new(
210 PointerEventKind::Down,
211 Point {
212 x: self.cursor.0,
213 y: self.cursor.1,
214 },
215 Point {
216 x: self.cursor.0,
217 y: self.cursor.1,
218 },
219 )
220 .with_buttons(self.buttons_pressed)
221 .with_time_ms(time_ms)
222 .with_source(self.pointer_source);
223
224 let mut delivered_capture_paths = Vec::new();
225 let mut applier = self.composition.applier_mut();
226 for hit in hits {
227 let node_id = hit.node_id();
228 delivered_capture_paths.push(hit.capture_path());
229 hit.dispatch_with_applier(&mut applier, event.clone());
230 log::trace!(
231 target: "cranpose::input",
232 "dispatch {:?} node={} consumed={} stop_on_consume=true",
233 event.kind,
234 node_id,
235 event.is_consumed(),
236 );
237 if event.is_consumed() {
238 break;
239 }
240 }
241
242 self.hit_path_tracker
243 .add_hit_path(PointerId::PRIMARY, delivered_capture_paths);
244 log::trace!(
245 target: "cranpose::input",
246 "pointer_pressed_inner cached_hit_path={:?}",
247 self.hit_path_tracker.get_path(PointerId::PRIMARY),
248 );
249
250 true
251 }
252 }
253
254 pub fn pointer_released(&mut self) -> bool {
255 self.pointer_released_at_time(None)
256 }
257
258 pub fn pointer_released_at_position(&mut self, x: f32, y: f32) -> bool {
271 self.pointer_released_at_position_time(x, y, None)
272 }
273
274 pub fn pointer_released_at_position_time(
278 &mut self,
279 x: f32,
280 y: f32,
281 time_ms: Option<i64>,
282 ) -> bool {
283 let _event_handler = enter_event_handler_scope();
284 let app_context = Rc::clone(&self.app_context);
285 let result = app_context.enter(|| {
286 run_in_mutable_snapshot(|| {
287 self.cursor = (x, y);
288 self.pointer_released_inner(time_ms)
289 })
290 .unwrap_or(false)
291 });
292 if result {
293 self.mark_dirty();
294 }
295 log::trace!(
296 target: "cranpose::input",
297 "pointer_released_at_position ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
298 );
299 result
300 }
301
302 pub fn pointer_released_at_time(&mut self, time_ms: Option<i64>) -> bool {
305 let _event_handler = enter_event_handler_scope();
306 let app_context = Rc::clone(&self.app_context);
307 let result = app_context.enter(|| {
308 run_in_mutable_snapshot(|| self.pointer_released_inner(time_ms)).unwrap_or(false)
309 });
310 if result {
311 self.mark_dirty();
312 }
313 log::trace!(target: "cranpose::input", "pointer_released time_ms={time_ms:?} -> {result}");
314 result
315 }
316
317 fn pointer_released_inner(&mut self, time_ms: Option<i64>) -> bool {
318 self.buttons_pressed.remove(PointerButton::Primary);
321 let corrected_buttons = self.buttons_pressed;
322 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
323
324 self.hit_path_tracker.remove_path(PointerId::PRIMARY);
326
327 if !targets.is_empty() {
328 let event = PointerEvent::new(
329 PointerEventKind::Up,
330 Point {
331 x: self.cursor.0,
332 y: self.cursor.1,
333 },
334 Point {
335 x: self.cursor.0,
336 y: self.cursor.1,
337 },
338 )
339 .with_buttons(corrected_buttons)
340 .with_time_ms(time_ms)
341 .with_source(self.pointer_source);
342
343 self.dispatch_targets(targets, event, false);
344 true
345 } else {
346 false
347 }
348 }
349
350 pub fn secondary_pointer_pressed(
359 &mut self,
360 pointer_id: u64,
361 x: f32,
362 y: f32,
363 time_ms: Option<i64>,
364 ) -> bool {
365 self.dispatch_secondary_pointer(PointerEventKind::Down, pointer_id, x, y, time_ms)
366 }
367
368 pub fn secondary_pointer_moved(
370 &mut self,
371 pointer_id: u64,
372 x: f32,
373 y: f32,
374 time_ms: Option<i64>,
375 ) -> bool {
376 self.dispatch_secondary_pointer(PointerEventKind::Move, pointer_id, x, y, time_ms)
377 }
378
379 pub fn secondary_pointer_released(
381 &mut self,
382 pointer_id: u64,
383 x: f32,
384 y: f32,
385 time_ms: Option<i64>,
386 ) -> bool {
387 self.dispatch_secondary_pointer(PointerEventKind::Up, pointer_id, x, y, time_ms)
388 }
389
390 fn dispatch_secondary_pointer(
391 &mut self,
392 kind: PointerEventKind,
393 pointer_id: u64,
394 x: f32,
395 y: f32,
396 time_ms: Option<i64>,
397 ) -> bool {
398 if pointer_id == 0 {
399 log::warn!(
400 target: "cranpose::input",
401 "secondary pointer dispatch called with the primary pointer id"
402 );
403 return false;
404 }
405
406 let _event_handler = enter_event_handler_scope();
407 let app_context = Rc::clone(&self.app_context);
408 let result = app_context.enter(|| {
409 run_in_mutable_snapshot(|| {
410 if !self.hit_path_tracker.has_path(PointerId::PRIMARY) {
411 return false;
412 }
413 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
414 if targets.is_empty() {
415 return false;
416 }
417 let pos = Point { x, y };
418 let event = PointerEvent::new(kind, pos, pos)
419 .with_buttons(self.buttons_pressed)
420 .with_time_ms(time_ms)
421 .with_id(pointer_id)
422 .with_source(self.pointer_source);
423 self.dispatch_targets(targets, event, false);
424 true
425 })
426 .unwrap_or(false)
427 });
428 if result {
429 self.mark_dirty();
430 }
431 log::trace!(
432 target: "cranpose::input",
433 "secondary_pointer {kind:?} id={pointer_id} ({x:.2},{y:.2}) time_ms={time_ms:?} -> {result}"
434 );
435 result
436 }
437
438 pub fn pointer_zoomed(&mut self, zoom_factor: f32) -> bool {
444 let _event_handler = enter_event_handler_scope();
445 let app_context = Rc::clone(&self.app_context);
446 let result = app_context.enter(|| {
447 run_in_mutable_snapshot(|| self.pointer_zoomed_inner(zoom_factor)).unwrap_or(false)
448 });
449 if result {
450 self.mark_dirty();
451 }
452 log::trace!(
453 target: "cranpose::input",
454 "pointer_zoomed factor={zoom_factor:.4} -> {result}"
455 );
456 result
457 }
458
459 fn pointer_zoomed_inner(&mut self, zoom_factor: f32) -> bool {
460 if !zoom_factor.is_finite() || zoom_factor <= 0.0 || zoom_factor == 1.0 {
461 return false;
462 }
463
464 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
465 if hits.is_empty() {
466 return false;
467 }
468
469 let pos = Point {
470 x: self.cursor.0,
471 y: self.cursor.1,
472 };
473 let event = PointerEvent::new(PointerEventKind::Zoom, pos, pos)
474 .with_buttons(self.buttons_pressed)
475 .with_zoom_delta(zoom_factor)
476 .with_source(self.pointer_source);
477
478 let capture_paths = hits
479 .iter()
480 .map(|hit| hit.capture_path())
481 .collect::<Vec<_>>();
482 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
483 .into_iter()
484 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
485 .collect::<Vec<_>>();
486
487 self.dispatch_targets(targets, event.clone(), true);
488
489 event.is_consumed()
490 }
491
492 pub fn pointer_scrolled(&mut self, delta_x: f32, delta_y: f32) -> bool {
496 let _event_handler = enter_event_handler_scope();
497 let app_context = Rc::clone(&self.app_context);
498 let result = app_context.enter(|| {
499 run_in_mutable_snapshot(|| self.pointer_scrolled_inner(delta_x, delta_y))
500 .unwrap_or(false)
501 });
502 if result {
503 self.mark_dirty();
504 }
505 log::trace!(
506 target: "cranpose::input",
507 "pointer_scrolled ({delta_x:.2},{delta_y:.2}) -> {result}"
508 );
509 result
510 }
511
512 fn pointer_scrolled_inner(&mut self, delta_x: f32, delta_y: f32) -> bool {
513 if delta_x.abs() <= f32::EPSILON && delta_y.abs() <= f32::EPSILON {
514 return false;
515 }
516
517 let hits = self.renderer.scene().hit_test(self.cursor.0, self.cursor.1);
518 if hits.is_empty() {
519 return false;
520 }
521
522 let event = PointerEvent::new(
523 PointerEventKind::Scroll,
524 Point {
525 x: self.cursor.0,
526 y: self.cursor.1,
527 },
528 Point {
529 x: self.cursor.0,
530 y: self.cursor.1,
531 },
532 )
533 .with_buttons(self.buttons_pressed)
534 .with_scroll_delta(Point {
535 x: delta_x,
536 y: delta_y,
537 })
538 .with_source(self.pointer_source);
539
540 let capture_paths = hits
541 .iter()
542 .map(|hit| hit.capture_path())
543 .collect::<Vec<_>>();
544 let targets = crate::hit_path_tracker::dispatch_order_for_paths(&capture_paths)
545 .into_iter()
546 .filter_map(|node_id| self.renderer.scene().find_target(node_id))
547 .collect::<Vec<_>>();
548
549 self.dispatch_targets(targets, event.clone(), true);
550
551 event.is_consumed()
552 }
553
554 pub fn cancel_gesture(&mut self) {
560 let _event_handler = enter_event_handler_scope();
561 let app_context = Rc::clone(&self.app_context);
562 let _ = app_context.enter(|| {
563 run_in_mutable_snapshot(|| {
564 self.cancel_gesture_inner();
565 })
566 });
567 }
568
569 fn cancel_gesture_inner(&mut self) {
570 let targets = self.resolve_gesture_targets(PointerId::PRIMARY);
571
572 self.hit_path_tracker.clear();
574 self.buttons_pressed = PointerButtons::NONE;
575
576 if !targets.is_empty() {
577 let event = PointerEvent::new(
578 PointerEventKind::Cancel,
579 Point {
580 x: self.cursor.0,
581 y: self.cursor.1,
582 },
583 Point {
584 x: self.cursor.0,
585 y: self.cursor.1,
586 },
587 )
588 .with_source(self.pointer_source);
589
590 self.dispatch_targets(targets, event, false);
591 }
592
593 let pos = Point {
595 x: self.cursor.0,
596 y: self.cursor.1,
597 };
598 let hovered_nodes = self.hovered_nodes.clone();
599 for node_id in hovered_nodes {
600 if let Some(target) = self.renderer.scene().find_target(node_id) {
601 let exit_event = PointerEvent::new(PointerEventKind::Exit, pos, pos)
602 .with_source(self.pointer_source);
603 self.dispatch_targets(std::iter::once(target), exit_event, false);
604 }
605 }
606 self.hovered_nodes.clear();
607 }
608
609 pub fn set_platform_text_input(
616 &mut self,
617 handler: Rc<dyn cranpose_ui::PlatformTextInputHandler>,
618 ) {
619 let app_context = Rc::clone(&self.app_context);
620 app_context
621 .enter(|| cranpose_ui::text_input_session::set_platform_text_input_handler(handler));
622 }
623
624 pub fn clear_platform_text_input(&mut self) {
626 let app_context = Rc::clone(&self.app_context);
627 app_context.enter(cranpose_ui::text_input_session::clear_platform_text_input_handler);
628 }
629
630 pub fn notify_app_paused(&mut self) {
637 let app_context = Rc::clone(&self.app_context);
638 app_context.enter(cranpose_ui::text_input_session::notify_app_paused);
639 }
640
641 pub fn notify_app_resumed(&mut self) -> bool {
649 let app_context = Rc::clone(&self.app_context);
650 app_context.enter(cranpose_ui::text_input_session::notify_app_resumed)
651 }
652
653 pub fn on_key_event(&mut self, event: &KeyEvent) -> bool {
660 let _event_handler = enter_event_handler_scope();
661 let app_context = Rc::clone(&self.app_context);
662 app_context.enter(|| self.on_key_event_inner(event))
663 }
664
665 fn on_key_event_inner(&mut self, event: &KeyEvent) -> bool {
667 use KeyEventType::KeyDown;
668
669 if event.event_type == KeyDown && event.modifiers.command_or_ctrl() {
671 #[cfg(all(
673 feature = "clipboard-native",
674 not(target_arch = "wasm32"),
675 not(target_os = "android"),
676 not(target_os = "ios")
677 ))]
678 {
679 match event.key_code {
680 KeyCode::C => {
682 let text = self.on_copy_inner();
684 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
685 let _ = clipboard.set_text(&text);
686 return true;
687 }
688 }
689 KeyCode::X => {
691 let text = self.on_cut_inner();
693 if let (Some(text), Some(clipboard)) = (text, self.clipboard.as_mut()) {
694 let _ = clipboard.set_text(&text);
695 self.mark_dirty();
696 self.request_layout_pass();
697 return true;
698 }
699 }
700 KeyCode::V => {
702 let text = self.clipboard.as_mut().and_then(|cb| cb.get_text().ok());
704 if let Some(text) = text {
705 if self.on_paste_inner(&text) {
706 return true;
707 }
708 }
709 }
710 _ => {}
711 }
712 }
713 }
714
715 if !cranpose_ui::text_field_focus::has_focused_field() {
717 return false;
718 }
719
720 let handled = run_in_mutable_snapshot(|| {
724 cranpose_ui::text_field_focus::dispatch_key_event(event)
727 })
728 .unwrap_or(false);
729
730 if handled {
731 self.mark_dirty();
733 self.request_layout_pass();
734 }
735
736 handled
737 }
738
739 pub fn on_paste(&mut self, text: &str) -> bool {
743 let _event_handler = enter_event_handler_scope();
744 let app_context = Rc::clone(&self.app_context);
745 app_context.enter(|| self.on_paste_inner(text))
746 }
747
748 fn on_paste_inner(&mut self, text: &str) -> bool {
749 let handled =
753 run_in_mutable_snapshot(|| cranpose_ui::text_field_focus::dispatch_paste(text))
754 .unwrap_or(false);
755
756 if handled {
757 self.mark_dirty();
758 self.request_layout_pass();
759 }
760
761 handled
762 }
763
764 pub fn on_copy(&mut self) -> Option<String> {
768 let app_context = Rc::clone(&self.app_context);
769 app_context.enter(|| self.on_copy_inner())
770 }
771
772 fn on_copy_inner(&mut self) -> Option<String> {
773 cranpose_ui::text_field_focus::dispatch_copy()
775 }
776
777 pub fn on_cut(&mut self) -> Option<String> {
781 let _event_handler = enter_event_handler_scope();
782 let app_context = Rc::clone(&self.app_context);
783 app_context.enter(|| self.on_cut_inner())
784 }
785
786 fn on_cut_inner(&mut self) -> Option<String> {
787 let text =
788 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_cut).unwrap_or(None);
789
790 if text.is_some() {
791 self.mark_dirty();
792 self.request_layout_pass();
793 }
794
795 text
796 }
797
798 #[cfg(all(
802 feature = "clipboard-native",
803 target_os = "linux",
804 not(target_arch = "wasm32")
805 ))]
806 pub fn set_primary_selection(&mut self, text: &str) {
807 use arboard::{LinuxClipboardKind, SetExtLinux};
808 if let Some(ref mut clipboard) = self.clipboard {
809 let result = clipboard
810 .set()
811 .clipboard(LinuxClipboardKind::Primary)
812 .text(text.to_string());
813 if let Err(e) = result {
814 log::debug!("Primary selection set failed: {:?}", e);
816 }
817 }
818 }
819
820 #[cfg(not(all(
821 feature = "clipboard-native",
822 target_os = "linux",
823 not(target_arch = "wasm32")
824 )))]
825 pub fn set_primary_selection(&mut self, _text: &str) {}
826
827 #[cfg(all(
830 feature = "clipboard-native",
831 target_os = "linux",
832 not(target_arch = "wasm32")
833 ))]
834 pub fn get_primary_selection(&mut self) -> Option<String> {
835 use arboard::{GetExtLinux, LinuxClipboardKind};
836 if let Some(ref mut clipboard) = self.clipboard {
837 clipboard
838 .get()
839 .clipboard(LinuxClipboardKind::Primary)
840 .text()
841 .ok()
842 } else {
843 None
844 }
845 }
846
847 #[cfg(not(all(
848 feature = "clipboard-native",
849 target_os = "linux",
850 not(target_arch = "wasm32")
851 )))]
852 pub fn get_primary_selection(&mut self) -> Option<String> {
853 None
854 }
855
856 pub fn sync_selection_to_primary(&mut self) {
859 #[cfg(all(target_os = "linux", not(target_arch = "wasm32")))]
860 {
861 if let Some(text) = self.on_copy() {
862 self.set_primary_selection(&text);
863 }
864 }
865 }
866
867 pub fn on_ime_preedit(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
875 let _event_handler = enter_event_handler_scope();
876 let app_context = Rc::clone(&self.app_context);
877 app_context.enter(|| self.on_ime_preedit_inner(text, cursor))
878 }
879
880 fn on_ime_preedit_inner(&mut self, text: &str, cursor: Option<(usize, usize)>) -> bool {
881 let handled = run_in_mutable_snapshot(|| {
883 cranpose_ui::text_field_focus::dispatch_ime_preedit(text, cursor)
884 })
885 .unwrap_or(false);
886
887 if handled {
888 self.mark_dirty();
889 self.request_layout_pass();
891 }
892
893 handled
894 }
895
896 pub fn on_ime_finish_composing(&mut self) -> bool {
900 let _event_handler = enter_event_handler_scope();
901 let app_context = Rc::clone(&self.app_context);
902 app_context.enter(|| self.on_ime_finish_composing_inner())
903 }
904
905 fn on_ime_finish_composing_inner(&mut self) -> bool {
906 let handled =
907 run_in_mutable_snapshot(cranpose_ui::text_field_focus::dispatch_ime_finish_composing)
908 .unwrap_or(false);
909
910 if handled {
911 self.mark_dirty();
912 self.request_layout_pass();
913 }
914
915 handled
916 }
917
918 pub fn on_ime_set_composing_region(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
922 let _event_handler = enter_event_handler_scope();
923 let app_context = Rc::clone(&self.app_context);
924 app_context.enter(|| {
925 let handled = run_in_mutable_snapshot(|| {
926 cranpose_ui::text_field_focus::dispatch_ime_set_composing_region(
927 start_bytes,
928 end_bytes,
929 )
930 })
931 .unwrap_or(false);
932
933 if handled {
934 self.mark_dirty();
935 self.request_layout_pass();
936 }
937
938 handled
939 })
940 }
941
942 pub fn on_ime_set_selection(&mut self, start_bytes: usize, end_bytes: usize) -> bool {
947 let _event_handler = enter_event_handler_scope();
948 let app_context = Rc::clone(&self.app_context);
949 app_context.enter(|| {
950 let handled = run_in_mutable_snapshot(|| {
951 cranpose_ui::text_field_focus::dispatch_ime_set_selection(start_bytes, end_bytes)
952 })
953 .unwrap_or(false);
954
955 if handled {
958 self.mark_dirty();
959 }
960
961 handled
962 })
963 }
964
965 pub fn ime_editor_state(&mut self) -> Option<cranpose_ui::text_field_focus::ImeEditorState> {
969 let app_context = Rc::clone(&self.app_context);
970 app_context.enter(cranpose_ui::text_field_focus::focused_editor_state)
971 }
972
973 pub fn ime_caret_geometry(
977 &mut self,
978 ) -> Option<cranpose_ui::text_field_focus::ImeCaretGeometry> {
979 let app_context = Rc::clone(&self.app_context);
980 app_context.enter(cranpose_ui::text_field_focus::focused_caret_geometry)
981 }
982
983 pub fn clear_text_field_focus(&mut self) {
986 let _event_handler = enter_event_handler_scope();
987 let app_context = Rc::clone(&self.app_context);
988 app_context.enter(cranpose_ui::text_field_focus::clear_focus);
989 self.mark_dirty();
990 self.request_layout_pass();
991 }
992
993 pub fn on_ime_delete_surrounding(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
996 let _event_handler = enter_event_handler_scope();
997 let app_context = Rc::clone(&self.app_context);
998 app_context.enter(|| self.on_ime_delete_surrounding_inner(before_bytes, after_bytes))
999 }
1000
1001 fn on_ime_delete_surrounding_inner(&mut self, before_bytes: usize, after_bytes: usize) -> bool {
1002 let handled = run_in_mutable_snapshot(|| {
1003 cranpose_ui::text_field_focus::dispatch_delete_surrounding(before_bytes, after_bytes)
1004 })
1005 .unwrap_or(false);
1006
1007 if handled {
1008 self.mark_dirty();
1009 self.request_layout_pass();
1010 }
1011
1012 handled
1013 }
1014}