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