1use std::{
11 cell::{Cell, RefCell},
12 rc::{Rc, Weak},
13};
14
15use crate::key_event::KeyEvent;
16
17#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct ImeEditorState {
24 pub text: String,
26 pub selection_start: usize,
28 pub selection_end: usize,
30 pub composition: Option<(usize, usize)>,
32 pub single_line: bool,
35}
36
37#[derive(Clone, Debug, PartialEq)]
42pub struct ImeCaretGeometry {
43 pub caret_xs: Vec<f32>,
46 pub top: f32,
48 pub line_height: f32,
50}
51
52pub trait FocusedTextFieldHandler {
55 fn node_id(&self) -> Option<cranpose_core::NodeId> {
61 None
62 }
63 fn handle_key(&self, event: &KeyEvent) -> bool;
65 fn insert_text(&self, text: &str);
67 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize);
69 fn copy_selection(&self) -> Option<String>;
71 fn cut_selection(&self) -> Option<String>;
73 fn select_all(&self) {}
75 fn set_composition(&self, text: &str, cursor: Option<(usize, usize)>);
80 fn finish_composition(&self) {}
84 fn set_composing_region(&self, start_bytes: usize, end_bytes: usize) {
89 let _ = (start_bytes, end_bytes);
90 }
91 fn set_selection(&self, start_bytes: usize, end_bytes: usize) {
97 let _ = (start_bytes, end_bytes);
98 }
99 fn editor_state(&self) -> Option<ImeEditorState> {
103 None
104 }
105 fn caret_geometry(&self) -> Option<ImeCaretGeometry> {
109 None
110 }
111}
112
113pub(crate) struct TextFieldFocusState {
114 focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
115 focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
116 focused_modal_depth: Cell<usize>,
120}
121
122impl TextFieldFocusState {
123 pub(crate) fn new() -> Self {
124 Self {
125 focused_field: RefCell::new(None),
126 focused_handler: RefCell::new(None),
127 focused_modal_depth: Cell::new(0),
128 }
129 }
130
131 fn request_focus(
132 &self,
133 is_focused: Rc<RefCell<bool>>,
134 handler: Rc<dyn FocusedTextFieldHandler>,
135 modal_depth: usize,
136 ) {
137 let mut current = self.focused_field.borrow_mut();
138
139 if let Some(ref weak) = *current
140 && let Some(old_focused) = weak.upgrade()
141 {
142 *old_focused.borrow_mut() = false;
143 }
144
145 *is_focused.borrow_mut() = true;
146 *current = Some(Rc::downgrade(&is_focused));
147 *self.focused_handler.borrow_mut() = Some(handler);
148 self.focused_modal_depth.set(modal_depth);
149 }
150
151 fn clear_focus(&self) {
152 let mut current = self.focused_field.borrow_mut();
153
154 if let Some(ref weak) = *current
155 && let Some(focused) = weak.upgrade()
156 {
157 *focused.borrow_mut() = false;
158 }
159
160 *current = None;
161 *self.focused_handler.borrow_mut() = None;
162 self.focused_modal_depth.set(0);
163 }
164
165 fn focused_at_depth(&self, depth: usize) -> bool {
167 self.has_focused_field() && self.focused_modal_depth.get() == depth
168 }
169
170 fn has_focused_field(&self) -> bool {
171 let mut current = self.focused_field.borrow_mut();
172 if let Some(ref weak) = *current {
173 if weak.upgrade().is_some() {
174 return true;
175 }
176 *current = None;
177 *self.focused_handler.borrow_mut() = None;
178 crate::cursor_animation::stop_cursor_blink();
179 }
180 false
181 }
182
183 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
184 if !self.has_focused_field() {
185 return None;
186 }
187 self.focused_handler.borrow().as_ref().cloned()
188 }
189
190 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
191 if let Some(handler) = self.focused_handler() {
192 handler.handle_key(event)
193 } else {
194 false
195 }
196 }
197
198 fn dispatch_paste(&self, text: &str) -> bool {
199 if let Some(handler) = self.focused_handler() {
200 handler.insert_text(text);
201 true
202 } else {
203 false
204 }
205 }
206
207 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
208 if let Some(handler) = self.focused_handler() {
209 handler.delete_surrounding(before_bytes, after_bytes);
210 true
211 } else {
212 false
213 }
214 }
215
216 fn dispatch_copy(&self) -> Option<String> {
217 self.focused_handler()
218 .and_then(|handler| handler.copy_selection())
219 }
220
221 fn dispatch_cut(&self) -> Option<String> {
222 self.focused_handler()
223 .and_then(|handler| handler.cut_selection())
224 }
225
226 fn dispatch_select_all(&self) -> bool {
227 if let Some(handler) = self.focused_handler() {
228 handler.select_all();
229 true
230 } else {
231 false
232 }
233 }
234
235 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
236 if let Some(handler) = self.focused_handler() {
237 handler.set_composition(text, cursor);
238 true
239 } else {
240 false
241 }
242 }
243
244 fn dispatch_ime_finish_composing(&self) -> bool {
245 if let Some(handler) = self.focused_handler() {
246 handler.finish_composition();
247 true
248 } else {
249 false
250 }
251 }
252
253 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
254 if let Some(handler) = self.focused_handler() {
255 handler.set_composing_region(start_bytes, end_bytes);
256 true
257 } else {
258 false
259 }
260 }
261
262 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
263 if let Some(handler) = self.focused_handler() {
264 handler.set_selection(start_bytes, end_bytes);
265 true
266 } else {
267 false
268 }
269 }
270
271 fn focused_editor_state(&self) -> Option<ImeEditorState> {
272 self.focused_handler()
273 .and_then(|handler| handler.editor_state())
274 }
275
276 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
277 self.focused_handler()
278 .and_then(|handler| handler.caret_geometry())
279 }
280}
281
282pub fn request_focus(
295 is_focused: Rc<RefCell<bool>>,
296 handler: Rc<dyn FocusedTextFieldHandler>,
297 modal_depth: usize,
298) {
299 if modal_depth < crate::modal::current_modal_depth() {
300 return;
301 }
302
303 let previous_field = focused_field_node();
308 let gaining_field = handler.node_id();
309
310 crate::render_state::with_text_field_focus(|state| {
311 state.request_focus(is_focused, handler, modal_depth)
312 });
313
314 for node_id in [previous_field, gaining_field].into_iter().flatten() {
315 crate::schedule_draw_repass(node_id);
316 }
317
318 crate::cursor_animation::start_cursor_blink();
320
321 crate::text_input_session::notify_text_input_focus_gained();
324
325 crate::request_render_invalidation();
328}
329
330pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
336 let owns_focus =
337 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
338 if owns_focus {
339 clear_focus();
340 }
341}
342
343pub fn clear_focus() {
345 if let Some(node_id) = focused_field_node() {
348 crate::schedule_draw_repass(node_id);
349 }
350 crate::render_state::with_text_field_focus(|state| state.clear_focus());
351
352 crate::cursor_animation::stop_cursor_blink();
354
355 crate::text_input_session::notify_text_input_focus_lost();
357
358 crate::request_render_invalidation();
359}
360
361pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
364 crate::render_state::with_text_field_focus(|state| {
365 state
366 .focused_handler()
367 .and_then(|handler| handler.node_id())
368 })
369}
370
371pub fn has_focused_field() -> bool {
374 let has_focus = crate::render_state::with_text_field_focus(|state| state.has_focused_field());
375 if !has_focus {
376 crate::text_input_session::notify_text_input_focus_lost();
380 }
381 has_focus
382}
383
384pub fn dispatch_key_event(event: &KeyEvent) -> bool {
391 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
392}
393
394pub fn dispatch_paste(text: &str) -> bool {
397 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
398}
399
400pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
403 crate::render_state::with_text_field_focus(|state| {
404 state.dispatch_delete_surrounding(before_bytes, after_bytes)
405 })
406}
407
408pub fn dispatch_copy() -> Option<String> {
411 crate::render_state::with_text_field_focus(|state| state.dispatch_copy())
412}
413
414pub fn dispatch_cut() -> Option<String> {
417 crate::render_state::with_text_field_focus(|state| state.dispatch_cut())
418}
419
420pub fn dispatch_select_all() -> bool {
423 crate::render_state::with_text_field_focus(|state| state.dispatch_select_all())
424}
425
426pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
430 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
431}
432
433pub fn dispatch_ime_finish_composing() -> bool {
437 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_finish_composing())
438}
439
440pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
444 crate::render_state::with_text_field_focus(|state| {
445 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
446 })
447}
448
449pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
454 crate::render_state::with_text_field_focus(|state| {
455 state.dispatch_ime_set_selection(start_bytes, end_bytes)
456 })
457}
458
459pub fn focused_editor_state() -> Option<ImeEditorState> {
463 crate::render_state::with_text_field_focus(|state| state.focused_editor_state())
464}
465
466pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
469 crate::render_state::with_text_field_focus(|state| state.focused_caret_geometry())
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 struct MockHandler;
478 impl FocusedTextFieldHandler for MockHandler {
479 fn handle_key(&self, _: &KeyEvent) -> bool {
480 false
481 }
482 fn insert_text(&self, _: &str) {}
483 fn delete_surrounding(&self, _: usize, _: usize) {}
484 fn copy_selection(&self) -> Option<String> {
485 None
486 }
487 fn cut_selection(&self) -> Option<String> {
488 None
489 }
490 fn set_composition(&self, _: &str, _: Option<(usize, usize)>) {}
491 }
492
493 fn mock_handler() -> Rc<dyn FocusedTextFieldHandler> {
494 Rc::new(MockHandler)
495 }
496
497 struct NodeBackedHandler(cranpose_core::NodeId);
500 impl FocusedTextFieldHandler for NodeBackedHandler {
501 fn node_id(&self) -> Option<cranpose_core::NodeId> {
502 Some(self.0)
503 }
504 fn handle_key(&self, _: &KeyEvent) -> bool {
505 false
506 }
507 fn insert_text(&self, _: &str) {}
508 fn delete_surrounding(&self, _: usize, _: usize) {}
509 fn copy_selection(&self) -> Option<String> {
510 None
511 }
512 fn cut_selection(&self) -> Option<String> {
513 None
514 }
515 fn set_composition(&self, _: &str, _: Option<(usize, usize)>) {}
516 }
517
518 #[test]
523 fn focus_transitions_schedule_scoped_draw_repasses_on_both_fields() {
524 let _app_context = crate::render_state::app_context_test_scope();
525 let _ = crate::render_state::take_draw_repass_nodes();
526
527 let first = Rc::new(RefCell::new(false));
528 request_focus(first.clone(), Rc::new(NodeBackedHandler(7)), 0);
529 assert!(
530 crate::render_state::take_draw_repass_nodes().contains(&7),
531 "gaining focus must re-record the gaining field's draws"
532 );
533
534 let second = Rc::new(RefCell::new(false));
535 request_focus(second.clone(), Rc::new(NodeBackedHandler(9)), 0);
536 let repasses = crate::render_state::take_draw_repass_nodes();
537 assert!(
538 repasses.contains(&7) && repasses.contains(&9),
539 "a focus hand-off must re-record both fields, got {repasses:?}"
540 );
541
542 clear_focus();
543 assert!(
544 crate::render_state::take_draw_repass_nodes().contains(&9),
545 "losing focus must re-record the field that had the caret"
546 );
547 }
548
549 #[test]
553 fn a_blink_transition_schedules_a_scoped_repass_on_the_focused_field() {
554 let _app_context = crate::render_state::app_context_test_scope();
555 let focus = Rc::new(RefCell::new(false));
556 request_focus(focus.clone(), Rc::new(NodeBackedHandler(21)), 0);
557 let _ = crate::render_state::take_draw_repass_nodes();
558
559 let past_interval = web_time::Instant::now()
560 + crate::cursor_animation::CursorAnimationState::BLINK_INTERVAL
561 + std::time::Duration::from_millis(1);
562 assert!(
563 crate::cursor_animation::tick_cursor_blink_at(past_interval),
564 "the tick past the interval must flip visibility"
565 );
566 assert!(
567 crate::render_state::take_draw_repass_nodes().contains(&21),
568 "the flip must re-record the focused field's draws"
569 );
570 clear_focus();
571 }
572
573 #[test]
574 fn request_focus_sets_flag() {
575 let _app_context = crate::render_state::app_context_test_scope();
576 let focus = Rc::new(RefCell::new(false));
577 request_focus(focus.clone(), mock_handler(), 0);
578 assert!(*focus.borrow());
579 clear_focus();
580 }
581
582 #[test]
583 fn request_focus_clears_previous() {
584 let _app_context = crate::render_state::app_context_test_scope();
585 let focus1 = Rc::new(RefCell::new(false));
586 let focus2 = Rc::new(RefCell::new(false));
587
588 request_focus(focus1.clone(), mock_handler(), 0);
589 assert!(*focus1.borrow());
590
591 request_focus(focus2.clone(), mock_handler(), 0);
592 assert!(!*focus1.borrow()); assert!(*focus2.borrow()); clear_focus();
595 }
596
597 #[test]
598 fn clear_focus_unfocuses_current() {
599 let _app_context = crate::render_state::app_context_test_scope();
600 let focus = Rc::new(RefCell::new(false));
601 request_focus(focus.clone(), mock_handler(), 0);
602 assert!(*focus.borrow());
603
604 clear_focus();
605 assert!(!*focus.borrow());
606 }
607
608 #[derive(Default)]
609 struct DispatchRecordingHandler {
610 key_count: Cell<usize>,
611 insert_count: Cell<usize>,
612 delete_count: Cell<usize>,
613 copy_count: Cell<usize>,
614 cut_count: Cell<usize>,
615 preedit_count: Cell<usize>,
616 last_delete: Cell<Option<(usize, usize)>>,
617 }
618
619 impl DispatchRecordingHandler {
620 fn bump(cell: &Cell<usize>) {
621 cell.set(cell.get() + 1);
622 }
623
624 fn total_calls(&self) -> usize {
625 self.key_count.get()
626 + self.insert_count.get()
627 + self.delete_count.get()
628 + self.copy_count.get()
629 + self.cut_count.get()
630 + self.preedit_count.get()
631 }
632 }
633
634 impl FocusedTextFieldHandler for DispatchRecordingHandler {
635 fn handle_key(&self, _: &KeyEvent) -> bool {
636 Self::bump(&self.key_count);
637 true
638 }
639
640 fn insert_text(&self, _: &str) {
641 Self::bump(&self.insert_count);
642 }
643
644 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize) {
645 Self::bump(&self.delete_count);
646 self.last_delete.set(Some((before_bytes, after_bytes)));
647 }
648
649 fn copy_selection(&self) -> Option<String> {
650 Self::bump(&self.copy_count);
651 Some("copy".to_string())
652 }
653
654 fn cut_selection(&self) -> Option<String> {
655 Self::bump(&self.cut_count);
656 Some("cut".to_string())
657 }
658
659 fn set_composition(&self, _: &str, _: Option<(usize, usize)>) {
660 Self::bump(&self.preedit_count);
661 }
662 }
663
664 #[test]
665 fn dispatch_delete_surrounding_calls_handler() {
666 let _app_context = crate::render_state::app_context_test_scope();
667 let focus = Rc::new(RefCell::new(false));
668 let handler = Rc::new(DispatchRecordingHandler::default());
669
670 request_focus(Rc::clone(&focus), handler.clone(), 0);
671 assert!(dispatch_delete_surrounding(3, 1));
672 assert_eq!(handler.last_delete.get(), Some((3, 1)));
673
674 clear_focus();
675 }
676
677 #[test]
678 fn dispatch_clears_stale_focus_owner_before_invoking_handler() {
679 let _app_context = crate::render_state::app_context_test_scope();
680 let handler = Rc::new(DispatchRecordingHandler::default());
681
682 {
683 let focus = Rc::new(RefCell::new(false));
684 request_focus(Rc::clone(&focus), handler.clone(), 0);
685 assert!(has_focused_field());
686 }
687
688 let key_event = KeyEvent::key_down(crate::key_event::KeyCode::A, "a");
689
690 assert!(!dispatch_key_event(&key_event));
691 assert!(!dispatch_paste("stale paste"));
692 assert!(!dispatch_delete_surrounding(2, 1));
693 assert_eq!(dispatch_copy(), None);
694 assert_eq!(dispatch_cut(), None);
695 assert!(!dispatch_ime_preedit("preedit", Some((1, 1))));
696 assert!(!has_focused_field());
697 assert_eq!(
698 handler.total_calls(),
699 0,
700 "stale focused-field handlers must not receive input"
701 );
702 }
703
704 #[derive(Default)]
705 struct KeyboardProbe {
706 calls: RefCell<Vec<&'static str>>,
707 }
708
709 impl crate::text_input_session::PlatformTextInputHandler for KeyboardProbe {
710 fn show_keyboard(&self) {
711 self.calls.borrow_mut().push("show");
712 }
713
714 fn hide_keyboard(&self) {
715 self.calls.borrow_mut().push("hide");
716 }
717 }
718
719 #[test]
720 fn focus_transitions_drive_platform_keyboard() {
721 let _app_context = crate::render_state::app_context_test_scope();
722 let keyboard = Rc::new(KeyboardProbe::default());
723 crate::text_input_session::set_platform_text_input_handler(keyboard.clone());
724
725 let focus = Rc::new(RefCell::new(false));
726 request_focus(focus.clone(), mock_handler(), 0);
727 assert_eq!(*keyboard.calls.borrow(), vec!["show"]);
728
729 request_focus(focus, mock_handler(), 0);
732 assert_eq!(*keyboard.calls.borrow(), vec!["show", "show"]);
733
734 clear_focus();
735 assert_eq!(*keyboard.calls.borrow(), vec!["show", "show", "hide"]);
736 }
737
738 #[test]
739 fn stale_focus_detection_hides_platform_keyboard() {
740 let _app_context = crate::render_state::app_context_test_scope();
741 let keyboard = Rc::new(KeyboardProbe::default());
742 crate::text_input_session::set_platform_text_input_handler(keyboard.clone());
743
744 {
745 let focus = Rc::new(RefCell::new(false));
746 request_focus(focus, mock_handler(), 0);
747 }
750
751 assert!(!has_focused_field());
752 assert_eq!(*keyboard.calls.borrow(), vec!["show", "hide"]);
753
754 assert!(!has_focused_field());
756 assert_eq!(*keyboard.calls.borrow(), vec!["show", "hide"]);
757 }
758
759 #[test]
760 fn text_field_focus_is_scoped_by_app_context() {
761 let _app_context = crate::render_state::app_context_test_scope();
762 let first = crate::render_state::AppContext::new_with_density(1.0);
763 let second = crate::render_state::AppContext::new_with_density(1.0);
764 let first_focus = Rc::new(RefCell::new(false));
765 let second_focus = Rc::new(RefCell::new(false));
766
767 first.enter(|| {
768 request_focus(first_focus.clone(), mock_handler(), 0);
769 assert!(has_focused_field());
770 assert!(*first_focus.borrow());
771 });
772
773 second.enter(|| {
774 assert!(!has_focused_field());
775 request_focus(second_focus.clone(), mock_handler(), 0);
776 assert!(has_focused_field());
777 assert!(*second_focus.borrow());
778 });
779
780 first.enter(|| {
781 assert!(has_focused_field());
782 assert!(*first_focus.borrow());
783 clear_focus();
784 assert!(!has_focused_field());
785 assert!(!*first_focus.borrow());
786 });
787
788 second.enter(|| {
789 assert!(has_focused_field());
790 assert!(*second_focus.borrow());
791 clear_focus();
792 });
793 }
794}