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