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 pub keyboard_options: crate::KeyboardOptions,
37}
38
39#[derive(Clone, Debug, Default, PartialEq)]
45pub struct ImeCaretGeometry {
46 pub caret_xs: Vec<f32>,
49 pub top: f32,
51 pub line_height: f32,
53 pub local_to_window: cranpose_ui_graphics::ProjectiveTransform,
55}
56
57impl ImeCaretGeometry {
58 pub fn caret_rect(&self, character: usize) -> cranpose_ui_graphics::Rect {
60 self.range_rect(character, character)
61 }
62
63 pub fn range_rect(&self, start: usize, end: usize) -> cranpose_ui_graphics::Rect {
65 let x = |index: usize| {
66 self.caret_xs
67 .get(index)
68 .or_else(|| self.caret_xs.last())
69 .copied()
70 .unwrap_or(0.0)
71 };
72 let (left, right) = (x(start), x(end));
73 self.local_to_window
74 .bounds_for_rect(cranpose_ui_graphics::Rect {
75 x: left.min(right),
76 y: self.top,
77 width: (right - left).abs().max(2.0),
78 height: self.line_height,
79 })
80 }
81
82 pub fn closest_character(&self, position: cranpose_ui_graphics::Point) -> Option<usize> {
85 let local = self.local_to_window.inverse()?.map_point(position);
86 self.caret_xs
87 .iter()
88 .enumerate()
89 .min_by(|(_, a), (_, b)| (*a - local.x).abs().total_cmp(&(*b - local.x).abs()))
90 .map(|(index, _)| index)
91 }
92}
93
94pub trait FocusedTextFieldHandler {
97 fn node_id(&self) -> Option<cranpose_core::NodeId> {
103 None
104 }
105 fn target_node(&self) -> Option<cranpose_core::NodeId> {
108 self.node_id()
109 }
110 fn show_keyboard_on_focus(&self) -> bool {
113 true
114 }
115 fn handle_key(&self, event: &KeyEvent) -> bool;
117 fn insert_text(&self, text: &str);
119 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize);
121 fn copy_selection(&self) -> Option<String>;
123 fn cut_selection(&self) -> Option<String>;
125 fn select_all(&self) {}
127 fn set_composition(&self, text: &str, cursor: Option<(usize, usize)>);
132 fn finish_composition(&self) {}
136 fn set_composing_region(&self, start_bytes: usize, end_bytes: usize) {
141 let _ = (start_bytes, end_bytes);
142 }
143 fn set_selection(&self, start_bytes: usize, end_bytes: usize) {
149 let _ = (start_bytes, end_bytes);
150 }
151 fn editor_state(&self) -> Option<ImeEditorState> {
155 None
156 }
157 fn perform_ime_action(&self, action: crate::ImeAction) -> bool {
162 let _ = action;
163 false
164 }
165 fn caret_geometry(&self) -> Option<ImeCaretGeometry> {
169 None
170 }
171}
172
173pub(crate) struct TextFieldFocusState {
174 focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
175 focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
176 focused_modal_depth: Cell<usize>,
177 edits: Cell<u64>,
178}
179
180impl TextFieldFocusState {
181 pub(crate) fn new() -> Self {
182 Self {
183 focused_field: RefCell::new(None),
184 focused_handler: RefCell::new(None),
185 focused_modal_depth: Cell::new(0),
186 edits: Cell::new(0),
187 }
188 }
189
190 fn request_focus(
191 &self,
192 is_focused: Rc<RefCell<bool>>,
193 handler: Rc<dyn FocusedTextFieldHandler>,
194 modal_depth: usize,
195 ) {
196 let mut current = self.focused_field.borrow_mut();
197
198 if let Some(ref weak) = *current
199 && let Some(old_focused) = weak.upgrade()
200 {
201 *old_focused.borrow_mut() = false;
202 }
203
204 *is_focused.borrow_mut() = true;
205 *current = Some(Rc::downgrade(&is_focused));
206 *self.focused_handler.borrow_mut() = Some(handler);
207 self.focused_modal_depth.set(modal_depth);
208 }
209
210 fn clear_focus(&self) {
211 let mut current = self.focused_field.borrow_mut();
212
213 if let Some(ref weak) = *current
214 && let Some(focused) = weak.upgrade()
215 {
216 *focused.borrow_mut() = false;
217 }
218
219 *current = None;
220 *self.focused_handler.borrow_mut() = None;
221 self.focused_modal_depth.set(0);
222 }
223
224 fn focused_at_depth(&self, depth: usize) -> bool {
225 self.has_focused_field() && self.focused_modal_depth.get() == depth
226 }
227
228 fn has_focused_field(&self) -> bool {
229 if self.focused_field_is_live() {
230 return true;
231 }
232 self.clear_stale_focus();
233 false
234 }
235
236 fn focused_field_is_live(&self) -> bool {
237 self.focused_field
238 .borrow()
239 .as_ref()
240 .is_some_and(|weak| weak.upgrade().is_some())
241 }
242
243 fn clear_stale_focus(&self) {
244 let stale_node = self
245 .focused_handler
246 .borrow()
247 .as_ref()
248 .and_then(|handler| handler.node_id());
249 let had_entry = self.focused_field.borrow_mut().take().is_some();
250 if !had_entry {
251 return;
252 }
253 self.focused_handler.borrow_mut().take();
254 if let Some(node_id) = stale_node {
255 crate::schedule_draw_repass(node_id);
256 }
257 crate::cursor_animation::stop_cursor_blink();
258 }
259
260 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
261 if !self.has_focused_field() {
262 return None;
263 }
264 self.focused_handler.borrow().as_ref().cloned()
265 }
266
267 fn editing_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
268 let handler = self.focused_handler();
269 if handler.is_some() {
270 self.note_edit();
271 }
272 handler
273 }
274
275 fn note_edit(&self) {
276 self.edits.set(self.edits.get().wrapping_add(1));
277 }
278
279 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
280 let handled = self
281 .focused_handler()
282 .is_some_and(|handler| handler.handle_key(event));
283 if handled {
284 self.note_edit();
285 }
286 handled
287 }
288
289 fn dispatch_paste(&self, text: &str) -> bool {
290 if let Some(handler) = self.editing_handler() {
291 handler.insert_text(text);
292 true
293 } else {
294 false
295 }
296 }
297
298 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
299 if let Some(handler) = self.editing_handler() {
300 handler.delete_surrounding(before_bytes, after_bytes);
301 true
302 } else {
303 false
304 }
305 }
306
307 fn dispatch_copy(&self) -> Option<String> {
308 self.focused_handler()
309 .and_then(|handler| handler.copy_selection())
310 }
311
312 fn dispatch_cut(&self) -> Option<String> {
313 self.editing_handler()
314 .and_then(|handler| handler.cut_selection())
315 }
316
317 fn dispatch_select_all(&self) -> bool {
318 if let Some(handler) = self.editing_handler() {
319 handler.select_all();
320 true
321 } else {
322 false
323 }
324 }
325
326 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
327 if let Some(handler) = self.editing_handler() {
328 handler.set_composition(text, cursor);
329 true
330 } else {
331 false
332 }
333 }
334
335 fn dispatch_ime_finish_composing(&self) -> bool {
336 if let Some(handler) = self.editing_handler() {
337 handler.finish_composition();
338 true
339 } else {
340 false
341 }
342 }
343
344 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
345 if let Some(handler) = self.editing_handler() {
346 handler.set_composing_region(start_bytes, end_bytes);
347 true
348 } else {
349 false
350 }
351 }
352
353 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
354 if let Some(handler) = self.editing_handler() {
355 handler.set_selection(start_bytes, end_bytes);
356 true
357 } else {
358 false
359 }
360 }
361
362 fn dispatch_ime_action(&self, action: crate::ImeAction) -> bool {
363 let handled = self
364 .focused_handler()
365 .is_some_and(|handler| handler.perform_ime_action(action));
366 if handled {
367 self.note_edit();
368 }
369 handled
370 }
371
372 fn focused_editor_state(&self) -> Option<ImeEditorState> {
373 self.focused_handler()
374 .and_then(|handler| handler.editor_state())
375 }
376
377 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
378 self.focused_handler()
379 .and_then(|handler| handler.caret_geometry())
380 }
381}
382
383pub fn request_focus(
396 is_focused: Rc<RefCell<bool>>,
397 handler: Rc<dyn FocusedTextFieldHandler>,
398 modal_depth: usize,
399) {
400 if modal_depth < crate::modal::current_modal_depth() {
401 return;
402 }
403
404 let previous_field = focused_field_node();
405 let gaining_field = handler.node_id();
406 let show_keyboard = handler.show_keyboard_on_focus();
407
408 crate::render_state::with_text_field_focus(|state| {
409 state.request_focus(is_focused, handler, modal_depth);
410 });
411
412 for node_id in [previous_field, gaining_field].into_iter().flatten() {
413 crate::schedule_draw_repass(node_id);
414 }
415
416 crate::cursor_animation::start_cursor_blink();
417
418 if show_keyboard {
419 crate::text_input_session::notify_text_input_focus_gained();
420 }
421
422 crate::request_render_invalidation();
423}
424
425pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
426 let owns_focus =
427 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
428 if owns_focus {
429 clear_focus();
430 }
431}
432
433pub fn clear_focus() {
435 let previous = focused_field_node();
436 let previous_target = focused_field_target();
437 if let Some(node_id) = previous {
438 crate::schedule_draw_repass(node_id);
439 }
440 crate::render_state::with_text_field_focus(TextFieldFocusState::clear_focus);
441 if previous_target.is_some() && crate::focus_dispatch::active_focus_target() == previous_target
442 {
443 crate::focus_dispatch::clear_active_focus();
444 }
445
446 crate::cursor_animation::stop_cursor_blink();
447
448 crate::text_input_session::notify_text_input_focus_lost();
449
450 crate::request_render_invalidation();
451}
452
453pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
456 crate::render_state::with_text_field_focus(|state| {
457 state
458 .focused_handler()
459 .and_then(|handler| handler.node_id())
460 })
461}
462
463pub fn focused_field_target() -> Option<cranpose_core::NodeId> {
466 crate::render_state::with_text_field_focus(|state| {
467 state
468 .focused_handler()
469 .and_then(|handler| handler.target_node())
470 })
471}
472
473pub fn has_focused_field() -> bool {
476 let has_focus =
477 crate::render_state::with_text_field_focus(TextFieldFocusState::has_focused_field);
478 if !has_focus {
479 crate::text_input_session::notify_text_input_focus_lost();
480 }
481 has_focus
482}
483
484pub fn dispatch_key_event(event: &KeyEvent) -> bool {
487 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
488}
489
490pub fn dispatch_paste(text: &str) -> bool {
493 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
494}
495
496pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
499 crate::render_state::with_text_field_focus(|state| {
500 state.dispatch_delete_surrounding(before_bytes, after_bytes)
501 })
502}
503
504pub fn dispatch_copy() -> Option<String> {
507 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_copy)
508}
509
510pub fn dispatch_cut() -> Option<String> {
513 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_cut)
514}
515
516pub fn dispatch_select_all() -> bool {
519 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_select_all)
520}
521
522pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
526 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
527}
528
529pub fn dispatch_ime_finish_composing() -> bool {
533 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_ime_finish_composing)
534}
535
536pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
540 crate::render_state::with_text_field_focus(|state| {
541 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
542 })
543}
544
545pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
550 crate::render_state::with_text_field_focus(|state| {
551 state.dispatch_ime_set_selection(start_bytes, end_bytes)
552 })
553}
554
555pub fn dispatch_ime_action(action: crate::ImeAction) -> bool {
559 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_action(action))
560}
561
562pub fn edit_count() -> u64 {
567 crate::render_state::with_text_field_focus(|state| state.edits.get())
568}
569
570pub fn focused_editor_state() -> Option<ImeEditorState> {
574 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_editor_state)
575}
576
577pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
580 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_caret_geometry)
581}
582
583#[cfg(test)]
584#[path = "tests/text_field_focus_tests.rs"]
585mod tests;