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 handle_key(&self, event: &KeyEvent) -> bool;
57 fn insert_text(&self, text: &str);
59 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize);
61 fn copy_selection(&self) -> Option<String>;
63 fn cut_selection(&self) -> Option<String>;
65 fn select_all(&self) {}
67 fn set_composition(&self, text: &str, cursor: Option<(usize, usize)>);
72 fn finish_composition(&self) {}
76 fn set_composing_region(&self, start_bytes: usize, end_bytes: usize) {
81 let _ = (start_bytes, end_bytes);
82 }
83 fn set_selection(&self, start_bytes: usize, end_bytes: usize) {
89 let _ = (start_bytes, end_bytes);
90 }
91 fn editor_state(&self) -> Option<ImeEditorState> {
95 None
96 }
97 fn caret_geometry(&self) -> Option<ImeCaretGeometry> {
101 None
102 }
103}
104
105pub(crate) struct TextFieldFocusState {
106 focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
107 focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
108 focused_modal_depth: Cell<usize>,
112}
113
114impl TextFieldFocusState {
115 pub(crate) fn new() -> Self {
116 Self {
117 focused_field: RefCell::new(None),
118 focused_handler: RefCell::new(None),
119 focused_modal_depth: Cell::new(0),
120 }
121 }
122
123 fn request_focus(
124 &self,
125 is_focused: Rc<RefCell<bool>>,
126 handler: Rc<dyn FocusedTextFieldHandler>,
127 modal_depth: usize,
128 ) {
129 let mut current = self.focused_field.borrow_mut();
130
131 if let Some(ref weak) = *current
132 && let Some(old_focused) = weak.upgrade()
133 {
134 *old_focused.borrow_mut() = false;
135 }
136
137 *is_focused.borrow_mut() = true;
138 *current = Some(Rc::downgrade(&is_focused));
139 *self.focused_handler.borrow_mut() = Some(handler);
140 self.focused_modal_depth.set(modal_depth);
141 }
142
143 fn clear_focus(&self) {
144 let mut current = self.focused_field.borrow_mut();
145
146 if let Some(ref weak) = *current
147 && let Some(focused) = weak.upgrade()
148 {
149 *focused.borrow_mut() = false;
150 }
151
152 *current = None;
153 *self.focused_handler.borrow_mut() = None;
154 self.focused_modal_depth.set(0);
155 }
156
157 fn focused_at_depth(&self, depth: usize) -> bool {
159 self.has_focused_field() && self.focused_modal_depth.get() == depth
160 }
161
162 fn has_focused_field(&self) -> bool {
163 let mut current = self.focused_field.borrow_mut();
164 if let Some(ref weak) = *current {
165 if weak.upgrade().is_some() {
166 return true;
167 }
168 *current = None;
169 *self.focused_handler.borrow_mut() = None;
170 crate::cursor_animation::stop_cursor_blink();
171 }
172 false
173 }
174
175 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
176 if !self.has_focused_field() {
177 return None;
178 }
179 self.focused_handler.borrow().as_ref().cloned()
180 }
181
182 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
183 if let Some(handler) = self.focused_handler() {
184 handler.handle_key(event)
185 } else {
186 false
187 }
188 }
189
190 fn dispatch_paste(&self, text: &str) -> bool {
191 if let Some(handler) = self.focused_handler() {
192 handler.insert_text(text);
193 true
194 } else {
195 false
196 }
197 }
198
199 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
200 if let Some(handler) = self.focused_handler() {
201 handler.delete_surrounding(before_bytes, after_bytes);
202 true
203 } else {
204 false
205 }
206 }
207
208 fn dispatch_copy(&self) -> Option<String> {
209 self.focused_handler()
210 .and_then(|handler| handler.copy_selection())
211 }
212
213 fn dispatch_cut(&self) -> Option<String> {
214 self.focused_handler()
215 .and_then(|handler| handler.cut_selection())
216 }
217
218 fn dispatch_select_all(&self) -> bool {
219 if let Some(handler) = self.focused_handler() {
220 handler.select_all();
221 true
222 } else {
223 false
224 }
225 }
226
227 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
228 if let Some(handler) = self.focused_handler() {
229 handler.set_composition(text, cursor);
230 true
231 } else {
232 false
233 }
234 }
235
236 fn dispatch_ime_finish_composing(&self) -> bool {
237 if let Some(handler) = self.focused_handler() {
238 handler.finish_composition();
239 true
240 } else {
241 false
242 }
243 }
244
245 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
246 if let Some(handler) = self.focused_handler() {
247 handler.set_composing_region(start_bytes, end_bytes);
248 true
249 } else {
250 false
251 }
252 }
253
254 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
255 if let Some(handler) = self.focused_handler() {
256 handler.set_selection(start_bytes, end_bytes);
257 true
258 } else {
259 false
260 }
261 }
262
263 fn focused_editor_state(&self) -> Option<ImeEditorState> {
264 self.focused_handler()
265 .and_then(|handler| handler.editor_state())
266 }
267
268 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
269 self.focused_handler()
270 .and_then(|handler| handler.caret_geometry())
271 }
272}
273
274pub fn request_focus(
287 is_focused: Rc<RefCell<bool>>,
288 handler: Rc<dyn FocusedTextFieldHandler>,
289 modal_depth: usize,
290) {
291 if modal_depth < crate::modal::current_modal_depth() {
292 return;
293 }
294
295 crate::render_state::with_text_field_focus(|state| {
296 state.request_focus(is_focused, handler, modal_depth)
297 });
298
299 crate::cursor_animation::start_cursor_blink();
301
302 crate::text_input_session::notify_text_input_focus_gained();
305
306 crate::request_render_invalidation();
309}
310
311pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
317 let owns_focus =
318 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
319 if owns_focus {
320 clear_focus();
321 }
322}
323
324pub fn clear_focus() {
326 crate::render_state::with_text_field_focus(|state| state.clear_focus());
327
328 crate::cursor_animation::stop_cursor_blink();
330
331 crate::text_input_session::notify_text_input_focus_lost();
333
334 crate::request_render_invalidation();
335}
336
337pub fn has_focused_field() -> bool {
340 let has_focus = crate::render_state::with_text_field_focus(|state| state.has_focused_field());
341 if !has_focus {
342 crate::text_input_session::notify_text_input_focus_lost();
346 }
347 has_focus
348}
349
350pub fn dispatch_key_event(event: &KeyEvent) -> bool {
357 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
358}
359
360pub fn dispatch_paste(text: &str) -> bool {
363 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
364}
365
366pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
369 crate::render_state::with_text_field_focus(|state| {
370 state.dispatch_delete_surrounding(before_bytes, after_bytes)
371 })
372}
373
374pub fn dispatch_copy() -> Option<String> {
377 crate::render_state::with_text_field_focus(|state| state.dispatch_copy())
378}
379
380pub fn dispatch_cut() -> Option<String> {
383 crate::render_state::with_text_field_focus(|state| state.dispatch_cut())
384}
385
386pub fn dispatch_select_all() -> bool {
389 crate::render_state::with_text_field_focus(|state| state.dispatch_select_all())
390}
391
392pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
396 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
397}
398
399pub fn dispatch_ime_finish_composing() -> bool {
403 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_finish_composing())
404}
405
406pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
410 crate::render_state::with_text_field_focus(|state| {
411 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
412 })
413}
414
415pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
420 crate::render_state::with_text_field_focus(|state| {
421 state.dispatch_ime_set_selection(start_bytes, end_bytes)
422 })
423}
424
425pub fn focused_editor_state() -> Option<ImeEditorState> {
429 crate::render_state::with_text_field_focus(|state| state.focused_editor_state())
430}
431
432pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
435 crate::render_state::with_text_field_focus(|state| state.focused_caret_geometry())
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441
442 struct MockHandler;
444 impl FocusedTextFieldHandler for MockHandler {
445 fn handle_key(&self, _: &KeyEvent) -> bool {
446 false
447 }
448 fn insert_text(&self, _: &str) {}
449 fn delete_surrounding(&self, _: usize, _: usize) {}
450 fn copy_selection(&self) -> Option<String> {
451 None
452 }
453 fn cut_selection(&self) -> Option<String> {
454 None
455 }
456 fn set_composition(&self, _: &str, _: Option<(usize, usize)>) {}
457 }
458
459 fn mock_handler() -> Rc<dyn FocusedTextFieldHandler> {
460 Rc::new(MockHandler)
461 }
462
463 #[test]
464 fn request_focus_sets_flag() {
465 let _app_context = crate::render_state::app_context_test_scope();
466 let focus = Rc::new(RefCell::new(false));
467 request_focus(focus.clone(), mock_handler(), 0);
468 assert!(*focus.borrow());
469 clear_focus();
470 }
471
472 #[test]
473 fn request_focus_clears_previous() {
474 let _app_context = crate::render_state::app_context_test_scope();
475 let focus1 = Rc::new(RefCell::new(false));
476 let focus2 = Rc::new(RefCell::new(false));
477
478 request_focus(focus1.clone(), mock_handler(), 0);
479 assert!(*focus1.borrow());
480
481 request_focus(focus2.clone(), mock_handler(), 0);
482 assert!(!*focus1.borrow()); assert!(*focus2.borrow()); clear_focus();
485 }
486
487 #[test]
488 fn clear_focus_unfocuses_current() {
489 let _app_context = crate::render_state::app_context_test_scope();
490 let focus = Rc::new(RefCell::new(false));
491 request_focus(focus.clone(), mock_handler(), 0);
492 assert!(*focus.borrow());
493
494 clear_focus();
495 assert!(!*focus.borrow());
496 }
497
498 #[derive(Default)]
499 struct DispatchRecordingHandler {
500 key_count: Cell<usize>,
501 insert_count: Cell<usize>,
502 delete_count: Cell<usize>,
503 copy_count: Cell<usize>,
504 cut_count: Cell<usize>,
505 preedit_count: Cell<usize>,
506 last_delete: Cell<Option<(usize, usize)>>,
507 }
508
509 impl DispatchRecordingHandler {
510 fn bump(cell: &Cell<usize>) {
511 cell.set(cell.get() + 1);
512 }
513
514 fn total_calls(&self) -> usize {
515 self.key_count.get()
516 + self.insert_count.get()
517 + self.delete_count.get()
518 + self.copy_count.get()
519 + self.cut_count.get()
520 + self.preedit_count.get()
521 }
522 }
523
524 impl FocusedTextFieldHandler for DispatchRecordingHandler {
525 fn handle_key(&self, _: &KeyEvent) -> bool {
526 Self::bump(&self.key_count);
527 true
528 }
529
530 fn insert_text(&self, _: &str) {
531 Self::bump(&self.insert_count);
532 }
533
534 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize) {
535 Self::bump(&self.delete_count);
536 self.last_delete.set(Some((before_bytes, after_bytes)));
537 }
538
539 fn copy_selection(&self) -> Option<String> {
540 Self::bump(&self.copy_count);
541 Some("copy".to_string())
542 }
543
544 fn cut_selection(&self) -> Option<String> {
545 Self::bump(&self.cut_count);
546 Some("cut".to_string())
547 }
548
549 fn set_composition(&self, _: &str, _: Option<(usize, usize)>) {
550 Self::bump(&self.preedit_count);
551 }
552 }
553
554 #[test]
555 fn dispatch_delete_surrounding_calls_handler() {
556 let _app_context = crate::render_state::app_context_test_scope();
557 let focus = Rc::new(RefCell::new(false));
558 let handler = Rc::new(DispatchRecordingHandler::default());
559
560 request_focus(Rc::clone(&focus), handler.clone(), 0);
561 assert!(dispatch_delete_surrounding(3, 1));
562 assert_eq!(handler.last_delete.get(), Some((3, 1)));
563
564 clear_focus();
565 }
566
567 #[test]
568 fn dispatch_clears_stale_focus_owner_before_invoking_handler() {
569 let _app_context = crate::render_state::app_context_test_scope();
570 let handler = Rc::new(DispatchRecordingHandler::default());
571
572 {
573 let focus = Rc::new(RefCell::new(false));
574 request_focus(Rc::clone(&focus), handler.clone(), 0);
575 assert!(has_focused_field());
576 }
577
578 let key_event = KeyEvent::key_down(crate::key_event::KeyCode::A, "a");
579
580 assert!(!dispatch_key_event(&key_event));
581 assert!(!dispatch_paste("stale paste"));
582 assert!(!dispatch_delete_surrounding(2, 1));
583 assert_eq!(dispatch_copy(), None);
584 assert_eq!(dispatch_cut(), None);
585 assert!(!dispatch_ime_preedit("preedit", Some((1, 1))));
586 assert!(!has_focused_field());
587 assert_eq!(
588 handler.total_calls(),
589 0,
590 "stale focused-field handlers must not receive input"
591 );
592 }
593
594 #[derive(Default)]
595 struct KeyboardProbe {
596 calls: RefCell<Vec<&'static str>>,
597 }
598
599 impl crate::text_input_session::PlatformTextInputHandler for KeyboardProbe {
600 fn show_keyboard(&self) {
601 self.calls.borrow_mut().push("show");
602 }
603
604 fn hide_keyboard(&self) {
605 self.calls.borrow_mut().push("hide");
606 }
607 }
608
609 #[test]
610 fn focus_transitions_drive_platform_keyboard() {
611 let _app_context = crate::render_state::app_context_test_scope();
612 let keyboard = Rc::new(KeyboardProbe::default());
613 crate::text_input_session::set_platform_text_input_handler(keyboard.clone());
614
615 let focus = Rc::new(RefCell::new(false));
616 request_focus(focus.clone(), mock_handler(), 0);
617 assert_eq!(*keyboard.calls.borrow(), vec!["show"]);
618
619 request_focus(focus, mock_handler(), 0);
622 assert_eq!(*keyboard.calls.borrow(), vec!["show", "show"]);
623
624 clear_focus();
625 assert_eq!(*keyboard.calls.borrow(), vec!["show", "show", "hide"]);
626 }
627
628 #[test]
629 fn stale_focus_detection_hides_platform_keyboard() {
630 let _app_context = crate::render_state::app_context_test_scope();
631 let keyboard = Rc::new(KeyboardProbe::default());
632 crate::text_input_session::set_platform_text_input_handler(keyboard.clone());
633
634 {
635 let focus = Rc::new(RefCell::new(false));
636 request_focus(focus, mock_handler(), 0);
637 }
640
641 assert!(!has_focused_field());
642 assert_eq!(*keyboard.calls.borrow(), vec!["show", "hide"]);
643
644 assert!(!has_focused_field());
646 assert_eq!(*keyboard.calls.borrow(), vec!["show", "hide"]);
647 }
648
649 #[test]
650 fn text_field_focus_is_scoped_by_app_context() {
651 let _app_context = crate::render_state::app_context_test_scope();
652 let first = crate::render_state::AppContext::new_with_density(1.0);
653 let second = crate::render_state::AppContext::new_with_density(1.0);
654 let first_focus = Rc::new(RefCell::new(false));
655 let second_focus = Rc::new(RefCell::new(false));
656
657 first.enter(|| {
658 request_focus(first_focus.clone(), mock_handler(), 0);
659 assert!(has_focused_field());
660 assert!(*first_focus.borrow());
661 });
662
663 second.enter(|| {
664 assert!(!has_focused_field());
665 request_focus(second_focus.clone(), mock_handler(), 0);
666 assert!(has_focused_field());
667 assert!(*second_focus.borrow());
668 });
669
670 first.enter(|| {
671 assert!(has_focused_field());
672 assert!(*first_focus.borrow());
673 clear_focus();
674 assert!(!has_focused_field());
675 assert!(!*first_focus.borrow());
676 });
677
678 second.enter(|| {
679 assert!(has_focused_field());
680 assert!(*second_focus.borrow());
681 clear_focus();
682 });
683 }
684}