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 fn window_bounds(&self) -> Option<cranpose_ui_graphics::Rect> {
175 None
176 }
177}
178
179pub(crate) struct TextFieldFocusState {
180 focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
181 focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
182 focused_modal_depth: Cell<usize>,
183 edits: Cell<u64>,
184}
185
186impl TextFieldFocusState {
187 pub(crate) fn new() -> Self {
188 Self {
189 focused_field: RefCell::new(None),
190 focused_handler: RefCell::new(None),
191 focused_modal_depth: Cell::new(0),
192 edits: Cell::new(0),
193 }
194 }
195
196 fn request_focus(
197 &self,
198 is_focused: Rc<RefCell<bool>>,
199 handler: Rc<dyn FocusedTextFieldHandler>,
200 modal_depth: usize,
201 ) {
202 let mut current = self.focused_field.borrow_mut();
203
204 if let Some(ref weak) = *current
205 && let Some(old_focused) = weak.upgrade()
206 {
207 *old_focused.borrow_mut() = false;
208 }
209
210 *is_focused.borrow_mut() = true;
211 *current = Some(Rc::downgrade(&is_focused));
212 *self.focused_handler.borrow_mut() = Some(handler);
213 self.focused_modal_depth.set(modal_depth);
214 }
215
216 fn clear_focus(&self) {
217 let mut current = self.focused_field.borrow_mut();
218
219 if let Some(ref weak) = *current
220 && let Some(focused) = weak.upgrade()
221 {
222 *focused.borrow_mut() = false;
223 }
224
225 *current = None;
226 *self.focused_handler.borrow_mut() = None;
227 self.focused_modal_depth.set(0);
228 }
229
230 fn focused_at_depth(&self, depth: usize) -> bool {
231 self.has_focused_field() && self.focused_modal_depth.get() == depth
232 }
233
234 fn has_focused_field(&self) -> bool {
235 if self.focused_field_is_live() {
236 return true;
237 }
238 self.clear_stale_focus();
239 false
240 }
241
242 fn focused_field_is_live(&self) -> bool {
243 self.focused_field
244 .borrow()
245 .as_ref()
246 .is_some_and(|weak| weak.upgrade().is_some())
247 }
248
249 fn clear_stale_focus(&self) {
250 let stale_node = self
251 .focused_handler
252 .borrow()
253 .as_ref()
254 .and_then(|handler| handler.node_id());
255 let had_entry = self.focused_field.borrow_mut().take().is_some();
256 if !had_entry {
257 return;
258 }
259 self.focused_handler.borrow_mut().take();
260 if let Some(node_id) = stale_node {
261 crate::schedule_draw_repass(node_id);
262 }
263 crate::cursor_animation::stop_cursor_blink();
264 }
265
266 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
267 if !self.has_focused_field() {
268 return None;
269 }
270 self.focused_handler.borrow().as_ref().cloned()
271 }
272
273 fn editing_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
274 let handler = self.focused_handler();
275 if handler.is_some() {
276 self.note_edit();
277 }
278 handler
279 }
280
281 fn note_edit(&self) {
282 self.edits.set(self.edits.get().wrapping_add(1));
283 }
284
285 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
286 let handled = self
287 .focused_handler()
288 .is_some_and(|handler| handler.handle_key(event));
289 if handled {
290 self.note_edit();
291 }
292 handled
293 }
294
295 fn dispatch_paste(&self, text: &str) -> bool {
296 if let Some(handler) = self.editing_handler() {
297 handler.insert_text(text);
298 true
299 } else {
300 false
301 }
302 }
303
304 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
305 if let Some(handler) = self.editing_handler() {
306 handler.delete_surrounding(before_bytes, after_bytes);
307 true
308 } else {
309 false
310 }
311 }
312
313 fn dispatch_copy(&self) -> Option<String> {
314 self.focused_handler()
315 .and_then(|handler| handler.copy_selection())
316 }
317
318 fn dispatch_cut(&self) -> Option<String> {
319 self.editing_handler()
320 .and_then(|handler| handler.cut_selection())
321 }
322
323 fn dispatch_select_all(&self) -> bool {
324 if let Some(handler) = self.editing_handler() {
325 handler.select_all();
326 true
327 } else {
328 false
329 }
330 }
331
332 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
333 if let Some(handler) = self.editing_handler() {
334 handler.set_composition(text, cursor);
335 true
336 } else {
337 false
338 }
339 }
340
341 fn dispatch_ime_finish_composing(&self) -> bool {
342 if let Some(handler) = self.editing_handler() {
343 handler.finish_composition();
344 true
345 } else {
346 false
347 }
348 }
349
350 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
351 if let Some(handler) = self.editing_handler() {
352 handler.set_composing_region(start_bytes, end_bytes);
353 true
354 } else {
355 false
356 }
357 }
358
359 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
360 if let Some(handler) = self.editing_handler() {
361 handler.set_selection(start_bytes, end_bytes);
362 true
363 } else {
364 false
365 }
366 }
367
368 fn dispatch_ime_action(&self, action: crate::ImeAction) -> bool {
369 let handled = self
370 .focused_handler()
371 .is_some_and(|handler| handler.perform_ime_action(action));
372 if handled {
373 self.note_edit();
374 }
375 handled
376 }
377
378 fn focused_editor_state(&self) -> Option<ImeEditorState> {
379 self.focused_handler()
380 .and_then(|handler| handler.editor_state())
381 }
382
383 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
384 self.focused_handler()
385 .and_then(|handler| handler.caret_geometry())
386 }
387
388 fn focused_window_bounds(&self) -> Option<cranpose_ui_graphics::Rect> {
389 self.focused_handler()
390 .and_then(|handler| handler.window_bounds())
391 }
392}
393
394pub fn request_focus(
407 is_focused: Rc<RefCell<bool>>,
408 handler: Rc<dyn FocusedTextFieldHandler>,
409 modal_depth: usize,
410) {
411 if modal_depth < crate::modal::current_modal_depth() {
412 return;
413 }
414
415 let previous_field = focused_field_node();
416 let gaining_field = handler.node_id();
417 let show_keyboard = handler.show_keyboard_on_focus();
418
419 crate::render_state::with_text_field_focus(|state| {
420 state.request_focus(is_focused, handler, modal_depth);
421 });
422
423 for node_id in [previous_field, gaining_field].into_iter().flatten() {
424 crate::schedule_draw_repass(node_id);
425 }
426
427 crate::cursor_animation::start_cursor_blink();
428
429 if show_keyboard {
430 crate::text_input_session::notify_text_input_focus_gained();
431 }
432
433 crate::request_render_invalidation();
434}
435
436pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
437 let owns_focus =
438 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
439 if owns_focus {
440 clear_focus();
441 }
442}
443
444pub fn clear_focus() {
446 let previous = focused_field_node();
447 let previous_target = focused_field_target();
448 if let Some(node_id) = previous {
449 crate::schedule_draw_repass(node_id);
450 }
451 crate::render_state::with_text_field_focus(TextFieldFocusState::clear_focus);
452 if previous_target.is_some() && crate::focus_dispatch::active_focus_target() == previous_target
453 {
454 crate::focus_dispatch::clear_active_focus();
455 }
456
457 crate::cursor_animation::stop_cursor_blink();
458
459 crate::text_input_session::notify_text_input_focus_lost();
460
461 crate::request_render_invalidation();
462}
463
464pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
467 crate::render_state::with_text_field_focus(|state| {
468 state
469 .focused_handler()
470 .and_then(|handler| handler.node_id())
471 })
472}
473
474pub fn focused_field_target() -> Option<cranpose_core::NodeId> {
477 crate::render_state::with_text_field_focus(|state| {
478 state
479 .focused_handler()
480 .and_then(|handler| handler.target_node())
481 })
482}
483
484pub fn has_focused_field() -> bool {
487 let has_focus =
488 crate::render_state::with_text_field_focus(TextFieldFocusState::has_focused_field);
489 if !has_focus {
490 crate::text_input_session::notify_text_input_focus_lost();
491 }
492 has_focus
493}
494
495pub fn dispatch_key_event(event: &KeyEvent) -> bool {
498 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
499}
500
501pub fn dispatch_paste(text: &str) -> bool {
504 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
505}
506
507pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
510 crate::render_state::with_text_field_focus(|state| {
511 state.dispatch_delete_surrounding(before_bytes, after_bytes)
512 })
513}
514
515pub fn dispatch_copy() -> Option<String> {
518 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_copy)
519}
520
521pub fn dispatch_cut() -> Option<String> {
524 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_cut)
525}
526
527pub fn dispatch_select_all() -> bool {
530 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_select_all)
531}
532
533pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
537 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
538}
539
540pub fn dispatch_ime_finish_composing() -> bool {
544 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_ime_finish_composing)
545}
546
547pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
551 crate::render_state::with_text_field_focus(|state| {
552 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
553 })
554}
555
556pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
561 crate::render_state::with_text_field_focus(|state| {
562 state.dispatch_ime_set_selection(start_bytes, end_bytes)
563 })
564}
565
566pub fn dispatch_ime_action(action: crate::ImeAction) -> bool {
570 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_action(action))
571}
572
573pub fn edit_count() -> u64 {
578 crate::render_state::with_text_field_focus(|state| state.edits.get())
579}
580
581pub fn focused_editor_state() -> Option<ImeEditorState> {
585 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_editor_state)
586}
587
588pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
591 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_caret_geometry)
592}
593
594pub fn focused_field_bounds() -> Option<cranpose_ui_graphics::Rect> {
597 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_window_bounds)
598}
599
600#[cfg(test)]
601#[path = "tests/text_field_focus_tests.rs"]
602mod tests;