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}
168
169impl TextFieldFocusState {
170 pub(crate) fn new() -> Self {
171 Self {
172 focused_field: RefCell::new(None),
173 focused_handler: RefCell::new(None),
174 focused_modal_depth: Cell::new(0),
175 }
176 }
177
178 fn request_focus(
179 &self,
180 is_focused: Rc<RefCell<bool>>,
181 handler: Rc<dyn FocusedTextFieldHandler>,
182 modal_depth: usize,
183 ) {
184 let mut current = self.focused_field.borrow_mut();
185
186 if let Some(ref weak) = *current
187 && let Some(old_focused) = weak.upgrade()
188 {
189 *old_focused.borrow_mut() = false;
190 }
191
192 *is_focused.borrow_mut() = true;
193 *current = Some(Rc::downgrade(&is_focused));
194 *self.focused_handler.borrow_mut() = Some(handler);
195 self.focused_modal_depth.set(modal_depth);
196 }
197
198 fn clear_focus(&self) {
199 let mut current = self.focused_field.borrow_mut();
200
201 if let Some(ref weak) = *current
202 && let Some(focused) = weak.upgrade()
203 {
204 *focused.borrow_mut() = false;
205 }
206
207 *current = None;
208 *self.focused_handler.borrow_mut() = None;
209 self.focused_modal_depth.set(0);
210 }
211
212 fn focused_at_depth(&self, depth: usize) -> bool {
213 self.has_focused_field() && self.focused_modal_depth.get() == depth
214 }
215
216 fn has_focused_field(&self) -> bool {
217 if self.focused_field_is_live() {
218 return true;
219 }
220 self.clear_stale_focus();
221 false
222 }
223
224 fn focused_field_is_live(&self) -> bool {
225 self.focused_field
226 .borrow()
227 .as_ref()
228 .is_some_and(|weak| weak.upgrade().is_some())
229 }
230
231 fn clear_stale_focus(&self) {
232 let stale_node = self
233 .focused_handler
234 .borrow()
235 .as_ref()
236 .and_then(|handler| handler.node_id());
237 let had_entry = self.focused_field.borrow_mut().take().is_some();
238 if !had_entry {
239 return;
240 }
241 self.focused_handler.borrow_mut().take();
242 if let Some(node_id) = stale_node {
243 crate::schedule_draw_repass(node_id);
244 }
245 crate::cursor_animation::stop_cursor_blink();
246 }
247
248 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
249 if !self.has_focused_field() {
250 return None;
251 }
252 self.focused_handler.borrow().as_ref().cloned()
253 }
254
255 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
256 if let Some(handler) = self.focused_handler() {
257 handler.handle_key(event)
258 } else {
259 false
260 }
261 }
262
263 fn dispatch_paste(&self, text: &str) -> bool {
264 if let Some(handler) = self.focused_handler() {
265 handler.insert_text(text);
266 true
267 } else {
268 false
269 }
270 }
271
272 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
273 if let Some(handler) = self.focused_handler() {
274 handler.delete_surrounding(before_bytes, after_bytes);
275 true
276 } else {
277 false
278 }
279 }
280
281 fn dispatch_copy(&self) -> Option<String> {
282 self.focused_handler()
283 .and_then(|handler| handler.copy_selection())
284 }
285
286 fn dispatch_cut(&self) -> Option<String> {
287 self.focused_handler()
288 .and_then(|handler| handler.cut_selection())
289 }
290
291 fn dispatch_select_all(&self) -> bool {
292 if let Some(handler) = self.focused_handler() {
293 handler.select_all();
294 true
295 } else {
296 false
297 }
298 }
299
300 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
301 if let Some(handler) = self.focused_handler() {
302 handler.set_composition(text, cursor);
303 true
304 } else {
305 false
306 }
307 }
308
309 fn dispatch_ime_finish_composing(&self) -> bool {
310 if let Some(handler) = self.focused_handler() {
311 handler.finish_composition();
312 true
313 } else {
314 false
315 }
316 }
317
318 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
319 if let Some(handler) = self.focused_handler() {
320 handler.set_composing_region(start_bytes, end_bytes);
321 true
322 } else {
323 false
324 }
325 }
326
327 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
328 if let Some(handler) = self.focused_handler() {
329 handler.set_selection(start_bytes, end_bytes);
330 true
331 } else {
332 false
333 }
334 }
335
336 fn focused_editor_state(&self) -> Option<ImeEditorState> {
337 self.focused_handler()
338 .and_then(|handler| handler.editor_state())
339 }
340
341 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
342 self.focused_handler()
343 .and_then(|handler| handler.caret_geometry())
344 }
345}
346
347pub fn request_focus(
360 is_focused: Rc<RefCell<bool>>,
361 handler: Rc<dyn FocusedTextFieldHandler>,
362 modal_depth: usize,
363) {
364 if modal_depth < crate::modal::current_modal_depth() {
365 return;
366 }
367
368 let previous_field = focused_field_node();
369 let gaining_field = handler.node_id();
370 let show_keyboard = handler.show_keyboard_on_focus();
371
372 crate::render_state::with_text_field_focus(|state| {
373 state.request_focus(is_focused, handler, modal_depth);
374 });
375
376 for node_id in [previous_field, gaining_field].into_iter().flatten() {
377 crate::schedule_draw_repass(node_id);
378 }
379
380 crate::cursor_animation::start_cursor_blink();
381
382 if show_keyboard {
383 crate::text_input_session::notify_text_input_focus_gained();
384 }
385
386 crate::request_render_invalidation();
387}
388
389pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
390 let owns_focus =
391 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
392 if owns_focus {
393 clear_focus();
394 }
395}
396
397pub fn clear_focus() {
399 let previous = focused_field_node();
400 let previous_target = focused_field_target();
401 if let Some(node_id) = previous {
402 crate::schedule_draw_repass(node_id);
403 }
404 crate::render_state::with_text_field_focus(TextFieldFocusState::clear_focus);
405 if previous_target.is_some() && crate::focus_dispatch::active_focus_target() == previous_target
406 {
407 crate::focus_dispatch::clear_active_focus();
408 }
409
410 crate::cursor_animation::stop_cursor_blink();
411
412 crate::text_input_session::notify_text_input_focus_lost();
413
414 crate::request_render_invalidation();
415}
416
417pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
420 crate::render_state::with_text_field_focus(|state| {
421 state
422 .focused_handler()
423 .and_then(|handler| handler.node_id())
424 })
425}
426
427pub fn focused_field_target() -> Option<cranpose_core::NodeId> {
430 crate::render_state::with_text_field_focus(|state| {
431 state
432 .focused_handler()
433 .and_then(|handler| handler.target_node())
434 })
435}
436
437pub fn has_focused_field() -> bool {
440 let has_focus =
441 crate::render_state::with_text_field_focus(TextFieldFocusState::has_focused_field);
442 if !has_focus {
443 crate::text_input_session::notify_text_input_focus_lost();
444 }
445 has_focus
446}
447
448pub fn dispatch_key_event(event: &KeyEvent) -> bool {
451 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
452}
453
454pub fn dispatch_paste(text: &str) -> bool {
457 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
458}
459
460pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
463 crate::render_state::with_text_field_focus(|state| {
464 state.dispatch_delete_surrounding(before_bytes, after_bytes)
465 })
466}
467
468pub fn dispatch_copy() -> Option<String> {
471 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_copy)
472}
473
474pub fn dispatch_cut() -> Option<String> {
477 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_cut)
478}
479
480pub fn dispatch_select_all() -> bool {
483 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_select_all)
484}
485
486pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
490 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
491}
492
493pub fn dispatch_ime_finish_composing() -> bool {
497 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_ime_finish_composing)
498}
499
500pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
504 crate::render_state::with_text_field_focus(|state| {
505 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
506 })
507}
508
509pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
514 crate::render_state::with_text_field_focus(|state| {
515 state.dispatch_ime_set_selection(start_bytes, end_bytes)
516 })
517}
518
519pub fn focused_editor_state() -> Option<ImeEditorState> {
523 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_editor_state)
524}
525
526pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
529 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_caret_geometry)
530}
531
532#[cfg(test)]
533#[path = "tests/text_field_focus_tests.rs"]
534mod tests;