cranpose_ui/
text_field_focus.rs1use 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 node_id(&self) -> Option<cranpose_core::NodeId> {
61 None
62 }
63 fn target_node(&self) -> Option<cranpose_core::NodeId> {
66 self.node_id()
67 }
68 fn handle_key(&self, event: &KeyEvent) -> bool;
70 fn insert_text(&self, text: &str);
72 fn delete_surrounding(&self, before_bytes: usize, after_bytes: usize);
74 fn copy_selection(&self) -> Option<String>;
76 fn cut_selection(&self) -> Option<String>;
78 fn select_all(&self) {}
80 fn set_composition(&self, text: &str, cursor: Option<(usize, usize)>);
85 fn finish_composition(&self) {}
89 fn set_composing_region(&self, start_bytes: usize, end_bytes: usize) {
94 let _ = (start_bytes, end_bytes);
95 }
96 fn set_selection(&self, start_bytes: usize, end_bytes: usize) {
102 let _ = (start_bytes, end_bytes);
103 }
104 fn editor_state(&self) -> Option<ImeEditorState> {
108 None
109 }
110 fn caret_geometry(&self) -> Option<ImeCaretGeometry> {
114 None
115 }
116}
117
118pub(crate) struct TextFieldFocusState {
119 focused_field: RefCell<Option<Weak<RefCell<bool>>>>,
120 focused_handler: RefCell<Option<Rc<dyn FocusedTextFieldHandler>>>,
121 focused_modal_depth: Cell<usize>,
122}
123
124impl TextFieldFocusState {
125 pub(crate) fn new() -> Self {
126 Self {
127 focused_field: RefCell::new(None),
128 focused_handler: RefCell::new(None),
129 focused_modal_depth: Cell::new(0),
130 }
131 }
132
133 fn request_focus(
134 &self,
135 is_focused: Rc<RefCell<bool>>,
136 handler: Rc<dyn FocusedTextFieldHandler>,
137 modal_depth: usize,
138 ) {
139 let mut current = self.focused_field.borrow_mut();
140
141 if let Some(ref weak) = *current
142 && let Some(old_focused) = weak.upgrade()
143 {
144 *old_focused.borrow_mut() = false;
145 }
146
147 *is_focused.borrow_mut() = true;
148 *current = Some(Rc::downgrade(&is_focused));
149 *self.focused_handler.borrow_mut() = Some(handler);
150 self.focused_modal_depth.set(modal_depth);
151 }
152
153 fn clear_focus(&self) {
154 let mut current = self.focused_field.borrow_mut();
155
156 if let Some(ref weak) = *current
157 && let Some(focused) = weak.upgrade()
158 {
159 *focused.borrow_mut() = false;
160 }
161
162 *current = None;
163 *self.focused_handler.borrow_mut() = None;
164 self.focused_modal_depth.set(0);
165 }
166
167 fn focused_at_depth(&self, depth: usize) -> bool {
168 self.has_focused_field() && self.focused_modal_depth.get() == depth
169 }
170
171 fn has_focused_field(&self) -> bool {
172 if self.focused_field_is_live() {
173 return true;
174 }
175 self.clear_stale_focus();
176 false
177 }
178
179 fn focused_field_is_live(&self) -> bool {
180 self.focused_field
181 .borrow()
182 .as_ref()
183 .is_some_and(|weak| weak.upgrade().is_some())
184 }
185
186 fn clear_stale_focus(&self) {
187 let stale_node = self
188 .focused_handler
189 .borrow()
190 .as_ref()
191 .and_then(|handler| handler.node_id());
192 let had_entry = self.focused_field.borrow_mut().take().is_some();
193 if !had_entry {
194 return;
195 }
196 self.focused_handler.borrow_mut().take();
197 if let Some(node_id) = stale_node {
198 crate::schedule_draw_repass(node_id);
199 }
200 crate::cursor_animation::stop_cursor_blink();
201 }
202
203 fn focused_handler(&self) -> Option<Rc<dyn FocusedTextFieldHandler>> {
204 if !self.has_focused_field() {
205 return None;
206 }
207 self.focused_handler.borrow().as_ref().cloned()
208 }
209
210 fn dispatch_key_event(&self, event: &KeyEvent) -> bool {
211 if let Some(handler) = self.focused_handler() {
212 handler.handle_key(event)
213 } else {
214 false
215 }
216 }
217
218 fn dispatch_paste(&self, text: &str) -> bool {
219 if let Some(handler) = self.focused_handler() {
220 handler.insert_text(text);
221 true
222 } else {
223 false
224 }
225 }
226
227 fn dispatch_delete_surrounding(&self, before_bytes: usize, after_bytes: usize) -> bool {
228 if let Some(handler) = self.focused_handler() {
229 handler.delete_surrounding(before_bytes, after_bytes);
230 true
231 } else {
232 false
233 }
234 }
235
236 fn dispatch_copy(&self) -> Option<String> {
237 self.focused_handler()
238 .and_then(|handler| handler.copy_selection())
239 }
240
241 fn dispatch_cut(&self) -> Option<String> {
242 self.focused_handler()
243 .and_then(|handler| handler.cut_selection())
244 }
245
246 fn dispatch_select_all(&self) -> bool {
247 if let Some(handler) = self.focused_handler() {
248 handler.select_all();
249 true
250 } else {
251 false
252 }
253 }
254
255 fn dispatch_ime_preedit(&self, text: &str, cursor: Option<(usize, usize)>) -> bool {
256 if let Some(handler) = self.focused_handler() {
257 handler.set_composition(text, cursor);
258 true
259 } else {
260 false
261 }
262 }
263
264 fn dispatch_ime_finish_composing(&self) -> bool {
265 if let Some(handler) = self.focused_handler() {
266 handler.finish_composition();
267 true
268 } else {
269 false
270 }
271 }
272
273 fn dispatch_ime_set_composing_region(&self, start_bytes: usize, end_bytes: usize) -> bool {
274 if let Some(handler) = self.focused_handler() {
275 handler.set_composing_region(start_bytes, end_bytes);
276 true
277 } else {
278 false
279 }
280 }
281
282 fn dispatch_ime_set_selection(&self, start_bytes: usize, end_bytes: usize) -> bool {
283 if let Some(handler) = self.focused_handler() {
284 handler.set_selection(start_bytes, end_bytes);
285 true
286 } else {
287 false
288 }
289 }
290
291 fn focused_editor_state(&self) -> Option<ImeEditorState> {
292 self.focused_handler()
293 .and_then(|handler| handler.editor_state())
294 }
295
296 fn focused_caret_geometry(&self) -> Option<ImeCaretGeometry> {
297 self.focused_handler()
298 .and_then(|handler| handler.caret_geometry())
299 }
300}
301
302pub fn request_focus(
315 is_focused: Rc<RefCell<bool>>,
316 handler: Rc<dyn FocusedTextFieldHandler>,
317 modal_depth: usize,
318) {
319 if modal_depth < crate::modal::current_modal_depth() {
320 return;
321 }
322
323 let previous_field = focused_field_node();
324 let gaining_field = handler.node_id();
325
326 crate::render_state::with_text_field_focus(|state| {
327 state.request_focus(is_focused, handler, modal_depth);
328 });
329
330 for node_id in [previous_field, gaining_field].into_iter().flatten() {
331 crate::schedule_draw_repass(node_id);
332 }
333
334 crate::cursor_animation::start_cursor_blink();
335
336 crate::text_input_session::notify_text_input_focus_gained();
337
338 crate::request_render_invalidation();
339}
340
341pub(crate) fn clear_focus_for_closed_modal(depth: usize) {
342 let owns_focus =
343 crate::render_state::with_text_field_focus(|state| state.focused_at_depth(depth));
344 if owns_focus {
345 clear_focus();
346 }
347}
348
349pub fn clear_focus() {
351 let previous = focused_field_node();
352 let previous_target = focused_field_target();
353 if let Some(node_id) = previous {
354 crate::schedule_draw_repass(node_id);
355 }
356 crate::render_state::with_text_field_focus(TextFieldFocusState::clear_focus);
357 if previous_target.is_some() && crate::focus_dispatch::active_focus_target() == previous_target
358 {
359 crate::focus_dispatch::clear_active_focus();
360 }
361
362 crate::cursor_animation::stop_cursor_blink();
363
364 crate::text_input_session::notify_text_input_focus_lost();
365
366 crate::request_render_invalidation();
367}
368
369pub fn focused_field_node() -> Option<cranpose_core::NodeId> {
372 crate::render_state::with_text_field_focus(|state| {
373 state
374 .focused_handler()
375 .and_then(|handler| handler.node_id())
376 })
377}
378
379pub fn focused_field_target() -> Option<cranpose_core::NodeId> {
382 crate::render_state::with_text_field_focus(|state| {
383 state
384 .focused_handler()
385 .and_then(|handler| handler.target_node())
386 })
387}
388
389pub fn has_focused_field() -> bool {
392 let has_focus =
393 crate::render_state::with_text_field_focus(TextFieldFocusState::has_focused_field);
394 if !has_focus {
395 crate::text_input_session::notify_text_input_focus_lost();
396 }
397 has_focus
398}
399
400pub fn dispatch_key_event(event: &KeyEvent) -> bool {
403 crate::render_state::with_text_field_focus(|state| state.dispatch_key_event(event))
404}
405
406pub fn dispatch_paste(text: &str) -> bool {
409 crate::render_state::with_text_field_focus(|state| state.dispatch_paste(text))
410}
411
412pub fn dispatch_delete_surrounding(before_bytes: usize, after_bytes: usize) -> bool {
415 crate::render_state::with_text_field_focus(|state| {
416 state.dispatch_delete_surrounding(before_bytes, after_bytes)
417 })
418}
419
420pub fn dispatch_copy() -> Option<String> {
423 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_copy)
424}
425
426pub fn dispatch_cut() -> Option<String> {
429 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_cut)
430}
431
432pub fn dispatch_select_all() -> bool {
435 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_select_all)
436}
437
438pub fn dispatch_ime_preedit(text: &str, cursor: Option<(usize, usize)>) -> bool {
442 crate::render_state::with_text_field_focus(|state| state.dispatch_ime_preedit(text, cursor))
443}
444
445pub fn dispatch_ime_finish_composing() -> bool {
449 crate::render_state::with_text_field_focus(TextFieldFocusState::dispatch_ime_finish_composing)
450}
451
452pub fn dispatch_ime_set_composing_region(start_bytes: usize, end_bytes: usize) -> bool {
456 crate::render_state::with_text_field_focus(|state| {
457 state.dispatch_ime_set_composing_region(start_bytes, end_bytes)
458 })
459}
460
461pub fn dispatch_ime_set_selection(start_bytes: usize, end_bytes: usize) -> bool {
466 crate::render_state::with_text_field_focus(|state| {
467 state.dispatch_ime_set_selection(start_bytes, end_bytes)
468 })
469}
470
471pub fn focused_editor_state() -> Option<ImeEditorState> {
475 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_editor_state)
476}
477
478pub fn focused_caret_geometry() -> Option<ImeCaretGeometry> {
481 crate::render_state::with_text_field_focus(TextFieldFocusState::focused_caret_geometry)
482}
483
484#[cfg(test)]
485#[path = "tests/text_field_focus_tests.rs"]
486mod tests;