agg_gui/widgets/text_field.rs
1//! `TextField` — single-line editable text input.
2//!
3//! See [`text_field_core`](super::text_field_core) for the internal helpers,
4//! shared edit state, and undo command type.
5//!
6//! Feature set mirrors C# agg-sharp `InternalTextEditWidget`:
7//! - Character / word navigation (arrows, Ctrl+arrow, Home, End)
8//! - Keyboard selection (Shift+movement), Ctrl+A select-all
9//! - Mouse click to position cursor, drag to extend selection
10//! - Double-click to select the word under the cursor
11//! - Cut / Copy / Paste (Ctrl+X/C/V, Shift+Del, Ctrl/Shift+Ins) — requires
12//! the `clipboard` crate feature; silently no-ops without it
13//! - Undo / Redo via the shared [`UndoBuffer`](crate::undo::UndoBuffer)
14//! - Blinking cursor (500 ms half-period from the moment focus is gained)
15//! - Horizontal scroll to keep cursor visible
16//! - Placeholder text, read-only mode, SelectAllOnFocus
17//! - Callbacks: on_change, on_enter, on_edit_complete
18
19use std::cell::{Cell, RefCell};
20use std::rc::Rc;
21use std::sync::Arc;
22
23// web-time provides a WASM-compatible Instant (uses performance.now() in the
24// browser; falls back to Instant on native).
25use web_time::Instant;
26
27use super::text_field_core::{
28 byte_at_x, next_char_boundary, next_word_boundary, prev_char_boundary, prev_word_boundary,
29 word_range_at, TextEditCommand, TextEditState,
30};
31use crate::color::Color;
32use crate::draw_ctx::DrawCtx;
33use crate::event::{Event, EventResult, Key, Modifiers, MouseButton};
34use crate::geometry::{Rect, Size};
35use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
36use crate::text::{measure_advance, Font};
37use crate::undo::UndoBuffer;
38use crate::widget::{BackbufferCache, BackbufferMode, Widget};
39
40// ---------------------------------------------------------------------------
41// Clipboard stubs
42// ---------------------------------------------------------------------------
43
44mod binding;
45mod clipboard;
46mod context_menu;
47mod filter;
48mod keyboard_mode;
49mod layout_builders;
50mod password;
51mod pointer;
52mod sig;
53mod theme;
54mod widget_impl;
55
56#[cfg(test)]
57mod selection_tests;
58
59use crate::widgets::text_context_menu::TextContextMenu;
60use clipboard::{clipboard_get, clipboard_set};
61use sig::TextFieldSig;
62pub use theme::TextFieldTheme;
63
64// ---------------------------------------------------------------------------
65// TextField
66// ---------------------------------------------------------------------------
67
68/// Single-line editable text field.
69pub struct TextField {
70 bounds: Rect,
71 children: Vec<Box<dyn Widget>>,
72 base: WidgetBase,
73
74 // All mutable editing state lives here — shared with undo commands.
75 edit: Rc<RefCell<TextEditState>>,
76
77 // Undo/redo history.
78 undo: UndoBuffer,
79
80 // Pending coalesced insert command (committed when action type changes).
81 pending_insert: Option<TextEditCommand>,
82
83 // Snapshot of text when focus was gained — used to decide if on_edit_complete fires.
84 text_on_focus: String,
85
86 // Font
87 font: Arc<Font>,
88 font_size: f64,
89
90 // Editing options
91 pub read_only: bool,
92 pub select_all_on_focus: bool,
93 /// When `true`, every character is displayed as '•' (U+2022).
94 /// The actual text is stored and edited normally; only the render is masked.
95 pub password_mode: bool,
96 /// Optional runtime "reveal" override for password fields. When the cell is
97 /// present and holds `true`, masking is suppressed and the plaintext shows
98 /// even though `password_mode` is set — this backs a demo-side eye toggle
99 /// without rebuilding the widget. `None`/`false` keeps the field masked.
100 password_reveal: Option<Rc<Cell<bool>>>,
101
102 // Interaction state
103 focused: bool,
104 hovered: bool,
105 mouse_down: bool,
106 scroll_x: f64,
107
108 // Cursor blink: set to Some(Instant::now()) on FocusGained.
109 focus_time: Option<Instant>,
110 // Blink phase (floor(elapsed_ms / 500)) last drawn by `paint_overlay`.
111 // `needs_draw` compares the current phase against this and reports
112 // dirty when they diverge — i.e. the host-observed time has crossed a
113 // flip boundary since the last paint. `Cell` so the check can happen
114 // from a `&self` method. Initialised far out of range so the first
115 // paint after focus always writes the real phase.
116 blink_last_phase: std::cell::Cell<u64>,
117
118 // Multi-click (single / double / triple) detection.
119 multi_click: crate::widgets::multi_click::MultiClickTracker,
120 // Granularity of the active selection drag (word/line drags extend by whole
121 // words/lines) plus the pivot range the initiating click selected.
122 select_granularity: crate::widgets::multi_click::SelectGranularity,
123 select_pivot: (usize, usize),
124
125 // Content
126 pub placeholder: String,
127
128 // Layout
129 pub padding: f64,
130
131 // Callbacks
132 on_change: Option<Box<dyn FnMut(&str)>>,
133 on_enter: Option<Box<dyn FnMut(&str)>>,
134 on_edit_complete: Option<Box<dyn FnMut(&str)>>,
135 text_cell: Option<Rc<RefCell<String>>>,
136
137 /// Per-character allow-list. See [`with_char_filter`].
138 char_filter: Option<Rc<dyn Fn(char) -> bool>>,
139
140 /// Stable id for the programmatic focus channel
141 /// ([`crate::focus::request_focus`]). `None` opts out. See
142 /// [`with_focus_id`](Self::with_focus_id).
143 focus_request_id: Option<crate::focus::FocusId>,
144
145 /// Preferred on-screen-keyboard layer when this field is focused.
146 /// `Rc<Cell<_>>` so external code (e.g. a settings radio in the
147 /// demo) can swap the mode without rebuilding the widget tree —
148 /// the next focus event picks up the new value. See
149 /// `text_field/keyboard_mode.rs` for the builders.
150 keyboard_mode: Rc<Cell<crate::widgets::on_screen_keyboard::KeyboardInputMode>>,
151
152 /// Per-widget colour overrides — `None` colours fall back to
153 /// the ambient `visuals()` palette. Set via [`with_theme`].
154 pub theme: TextFieldTheme,
155
156 // ── Backbuffer cache ─────────────────────────────────────────────
157 //
158 // Cache holds bg + text + selection + border. Cursor draws in
159 // `paint_overlay` directly on the outer ctx AFTER the cache blit
160 // so cursor-blink state flips (twice per second) don't invalidate
161 // the cache. Sig deliberately excludes `blink_visible`.
162 cache: BackbufferCache,
163 last_sig: Option<TextFieldSig>,
164
165 /// Default-on right-click Cut/Copy/Paste/Select-All menu. Opt out with
166 /// [`with_context_menu(false)`](Self::with_context_menu).
167 context_menu: TextContextMenu,
168 context_menu_enabled: bool,
169}
170
171impl TextField {
172 pub fn new(font: Arc<Font>) -> Self {
173 Self {
174 bounds: Rect::default(),
175 children: Vec::new(),
176 base: WidgetBase::new(),
177 edit: Rc::new(RefCell::new(TextEditState::default())),
178 undo: UndoBuffer::new(),
179 pending_insert: None,
180 text_on_focus: String::new(),
181 font,
182 font_size: 14.0,
183 read_only: false,
184 select_all_on_focus: false,
185 password_mode: false,
186 password_reveal: None,
187 focused: false,
188 hovered: false,
189 mouse_down: false,
190 scroll_x: 0.0,
191 focus_time: None,
192 blink_last_phase: std::cell::Cell::new(u64::MAX),
193 multi_click: crate::widgets::multi_click::MultiClickTracker::default(),
194 select_granularity: crate::widgets::multi_click::SelectGranularity::default(),
195 select_pivot: (0, 0),
196 placeholder: String::new(),
197 padding: 8.0,
198 on_change: None,
199 on_enter: None,
200 on_edit_complete: None,
201 text_cell: None,
202 char_filter: None,
203 focus_request_id: None,
204 keyboard_mode: Rc::new(Cell::new(
205 crate::widgets::on_screen_keyboard::KeyboardInputMode::default(),
206 )),
207 theme: TextFieldTheme::default(),
208 cache: BackbufferCache::default(),
209 last_sig: None,
210 context_menu: TextContextMenu::new(),
211 context_menu_enabled: true,
212 }
213 }
214
215 /// Currently-active font — honours the thread-local system-font override
216 /// (`font_settings::current_system_font`) so changes in the System window
217 /// propagate live without a widget-tree rebuild. Falls back to the font
218 /// passed at construction when no override is set.
219 fn active_font(&self) -> Arc<Font> {
220 crate::font_settings::current_system_font().unwrap_or_else(|| Arc::clone(&self.font))
221 }
222
223 // ── Builder / setter methods ─────────────────────────────────────────────
224
225 pub fn with_font_size(mut self, s: f64) -> Self {
226 self.font_size = s;
227 self
228 }
229 pub fn with_padding(mut self, p: f64) -> Self {
230 self.padding = p;
231 self
232 }
233 pub fn with_read_only(mut self, v: bool) -> Self {
234 self.read_only = v;
235 self
236 }
237 /// Enable or disable the default right-click Cut/Copy/Paste/Select-All
238 /// context menu. On by default.
239 pub fn with_context_menu(mut self, v: bool) -> Self {
240 self.context_menu_enabled = v;
241 self
242 }
243 pub fn with_select_all_on_focus(mut self, v: bool) -> Self {
244 self.select_all_on_focus = v;
245 self
246 }
247
248 // Password-mode builders + the `masking_active` helper live in
249 // `text_field/password.rs` to keep this file under the 800-line cap.
250
251 pub fn with_placeholder(mut self, s: impl Into<String>) -> Self {
252 self.placeholder = s.into();
253 self
254 }
255 pub fn with_text(self, s: impl Into<String>) -> Self {
256 let t = s.into();
257 let len = t.len();
258 let mut st = self.edit.borrow_mut();
259 st.text = t;
260 st.cursor = len;
261 st.anchor = len;
262 drop(st);
263 self
264 }
265
266 pub fn on_change(mut self, cb: impl FnMut(&str) + 'static) -> Self {
267 self.on_change = Some(Box::new(cb));
268 self
269 }
270 pub fn on_enter(mut self, cb: impl FnMut(&str) + 'static) -> Self {
271 self.on_enter = Some(Box::new(cb));
272 self
273 }
274 pub fn on_edit_complete(mut self, cb: impl FnMut(&str) + 'static) -> Self {
275 self.on_edit_complete = Some(Box::new(cb));
276 self
277 }
278
279 // Layout-trait builders (with_margin / with_h_anchor / with_v_anchor /
280 // with_min_size / with_max_size) live in `text_field/layout_builders.rs`
281 // so this file stays under the project's 800-line cap.
282
283 // ── Getters ──────────────────────────────────────────────────────────────
284
285 pub fn text(&self) -> String {
286 self.edit.borrow().text.clone()
287 }
288 pub fn cursor_pos(&self) -> usize {
289 self.edit.borrow().cursor
290 }
291 pub fn selection(&self) -> String {
292 let st = self.edit.borrow();
293 let lo = st.cursor.min(st.anchor);
294 let hi = st.cursor.max(st.anchor);
295 st.text[lo..hi].to_string()
296 }
297
298 pub fn set_text(&mut self, s: impl Into<String>) {
299 let t = s.into();
300 let len = t.len();
301 let mut st = self.edit.borrow_mut();
302 st.text = t.clone();
303 st.cursor = len;
304 st.anchor = len;
305 drop(st);
306 if let Some(cell) = &self.text_cell {
307 *cell.borrow_mut() = t;
308 }
309 self.undo.clear_history();
310 self.pending_insert = None;
311 }
312
313 // ── Private state helpers ────────────────────────────────────────────────
314
315 fn snap(&self) -> TextEditState {
316 self.edit.borrow().clone()
317 }
318 #[allow(dead_code)]
319 fn apply(&self, s: TextEditState) {
320 *self.edit.borrow_mut() = s;
321 }
322
323 #[allow(dead_code)]
324 fn sel_min(&self) -> usize {
325 let s = self.edit.borrow();
326 s.cursor.min(s.anchor)
327 }
328 #[allow(dead_code)]
329 fn sel_max(&self) -> usize {
330 let s = self.edit.borrow();
331 s.cursor.max(s.anchor)
332 }
333 fn has_selection(&self) -> bool {
334 let s = self.edit.borrow();
335 s.cursor != s.anchor
336 }
337
338 /// Commit any pending coalesced insert command to the undo buffer.
339 fn flush_pending(&mut self) {
340 if let Some(cmd) = self.pending_insert.take() {
341 self.undo.add(Box::new(cmd));
342 }
343 }
344
345 // ── Edit operations ──────────────────────────────────────────────────────
346
347 /// Insert `s` at cursor, replacing any selection.
348 /// Consecutive single-char inserts are coalesced into one undo command.
349 fn do_insert(&mut self, s: &str, is_single_char: bool) {
350 // Strip disallowed chars; bail when nothing survives.
351 let filtered = self.apply_char_filter(s);
352 if filtered.is_empty() {
353 return;
354 }
355 let s = filtered.as_str();
356 let before = self.snap();
357 let had_selection = before.cursor != before.anchor;
358
359 // Apply the change
360 {
361 let mut st = self.edit.borrow_mut();
362 if st.cursor != st.anchor {
363 let lo = st.cursor.min(st.anchor);
364 let hi = st.cursor.max(st.anchor);
365 st.text.drain(lo..hi);
366 st.cursor = lo;
367 st.anchor = lo;
368 }
369 let cursor = st.cursor;
370 st.text.insert_str(cursor, s);
371 st.cursor = cursor + s.len();
372 st.anchor = st.cursor;
373 }
374
375 let after = self.snap();
376
377 if is_single_char && !had_selection {
378 // Extend the pending coalesced command if one exists, otherwise start one.
379 if let Some(ref mut pending) = self.pending_insert {
380 pending.after = after;
381 } else {
382 self.pending_insert = Some(TextEditCommand {
383 name: "insert text",
384 before,
385 after,
386 target: Rc::clone(&self.edit),
387 });
388 }
389 } else {
390 // Non-char insert (paste) or insert-over-selection: commit pending and push new.
391 self.flush_pending();
392 self.undo.add(Box::new(TextEditCommand {
393 name: "insert text",
394 before,
395 after,
396 target: Rc::clone(&self.edit),
397 }));
398 }
399
400 self.ensure_cursor_visible();
401 self.notify_change();
402 }
403
404 /// Delete the selection (if any) or a single char/word, then push undo.
405 fn do_delete(&mut self, forward: bool, word: bool) {
406 self.flush_pending();
407 let before = self.snap();
408 {
409 let mut st = self.edit.borrow_mut();
410 if st.cursor != st.anchor {
411 let lo = st.cursor.min(st.anchor);
412 let hi = st.cursor.max(st.anchor);
413 st.text.drain(lo..hi);
414 st.cursor = lo;
415 st.anchor = lo;
416 } else if forward {
417 let cursor = st.cursor;
418 let end = if word {
419 next_word_boundary(&st.text, cursor)
420 } else {
421 next_char_boundary(&st.text, cursor)
422 };
423 if end > cursor {
424 st.text.drain(cursor..end);
425 }
426 st.anchor = st.cursor;
427 } else {
428 let cursor = st.cursor;
429 let start = if word {
430 prev_word_boundary(&st.text, cursor)
431 } else {
432 prev_char_boundary(&st.text, cursor)
433 };
434 if start < cursor {
435 st.text.drain(start..cursor);
436 st.cursor = start;
437 st.anchor = start;
438 }
439 }
440 }
441 let after = self.snap();
442 self.undo.add(Box::new(TextEditCommand {
443 name: "delete text",
444 before,
445 after,
446 target: Rc::clone(&self.edit),
447 }));
448 self.ensure_cursor_visible();
449 self.notify_change();
450 }
451
452 fn do_undo(&mut self) {
453 self.flush_pending();
454 self.undo.undo();
455 // Clamp positions in case the text changed length.
456 let len = self.edit.borrow().text.len();
457 let mut st = self.edit.borrow_mut();
458 st.cursor = st.cursor.min(len);
459 st.anchor = st.anchor.min(len);
460 drop(st);
461 self.ensure_cursor_visible();
462 self.notify_change();
463 }
464
465 fn do_redo(&mut self) {
466 self.flush_pending();
467 self.undo.redo();
468 let len = self.edit.borrow().text.len();
469 let mut st = self.edit.borrow_mut();
470 st.cursor = st.cursor.min(len);
471 st.anchor = st.anchor.min(len);
472 drop(st);
473 self.ensure_cursor_visible();
474 self.notify_change();
475 }
476
477 // Callback dispatchers — see `text_field/filter.rs`.
478
479 // ── Keyboard handler ─────────────────────────────────────────────────────
480
481 fn handle_key(&mut self, key: &Key, mods: Modifiers) -> EventResult {
482 // Snapshot cursor/anchor before movement so we can keep anchor on Shift.
483 let anchor_before = self.edit.borrow().anchor;
484
485 // Command modifier (clipboard / select-all / undo): `Ctrl` on Windows
486 // and Linux, `Cmd` (meta) on macOS. Treating the two as equivalent
487 // means the same handler serves both OSes without branching.
488 let cmd = mods.ctrl || mods.meta;
489 // Word-navigation modifier: `Ctrl` on Windows/Linux, `Option`
490 // (alt) on macOS. Used for Ctrl/Alt+Arrow, Ctrl/Alt+Backspace,
491 // Ctrl/Alt+Delete.
492 let word = mods.ctrl || mods.alt;
493
494 match key {
495 // ── Printable characters (and Ctrl/Cmd shortcuts on Char) ──────
496 Key::Char(c) if !self.read_only || cmd => {
497 if cmd {
498 return match c {
499 'a' | 'A' => {
500 self.select_all_text();
501 EventResult::Consumed
502 }
503 'z' | 'Z' if !mods.shift => {
504 if !self.read_only {
505 self.do_undo();
506 }
507 EventResult::Consumed
508 }
509 'z' | 'Z' | 'y' | 'Y' => {
510 if !self.read_only {
511 self.do_redo();
512 }
513 EventResult::Consumed
514 }
515 'x' | 'X' => {
516 self.clipboard_cut();
517 EventResult::Consumed
518 }
519 'c' | 'C' => {
520 self.clipboard_copy();
521 EventResult::Consumed
522 }
523 'v' | 'V' => {
524 self.clipboard_paste();
525 EventResult::Consumed
526 }
527 _ => EventResult::Ignored,
528 };
529 }
530 if self.read_only {
531 return EventResult::Ignored;
532 }
533 let mut buf = [0u8; 4];
534 let s = c.encode_utf8(&mut buf);
535 self.do_insert(s, true);
536 EventResult::Consumed
537 }
538
539 // ── Insert clipboard shortcuts ────────────────────────────────
540 // Classic Windows bindings (still common on Linux):
541 // Shift+Insert = Paste
542 // Ctrl+Insert = Copy
543 // Plain `Insert` toggles overwrite mode in many editors — we
544 // don't model overwrite, so plain Insert is a no-op here.
545 Key::Insert => {
546 if mods.shift {
547 self.clipboard_paste();
548 return EventResult::Consumed;
549 }
550 if cmd {
551 self.clipboard_copy();
552 return EventResult::Consumed;
553 }
554 EventResult::Ignored
555 }
556
557 // ── Backspace ─────────────────────────────────────────────────
558 Key::Backspace if !self.read_only => {
559 self.do_delete(false, word);
560 EventResult::Consumed
561 }
562
563 // ── Delete ────────────────────────────────────────────────────
564 Key::Delete if !self.read_only => {
565 if mods.shift {
566 // Shift+Delete = Cut
567 self.clipboard_cut();
568 } else {
569 self.do_delete(true, word);
570 }
571 EventResult::Consumed
572 }
573
574 // ── Arrow Left ────────────────────────────────────────────────
575 // Mac: `Cmd+Left` = start of line (Home behaviour).
576 // Win/Mac: `Ctrl+Left` / `Option+Left` = previous word.
577 // Plain: one character back (or collapse selection to left).
578 Key::ArrowLeft => {
579 self.flush_pending();
580 let (cur, anchor) = {
581 let st = self.edit.borrow();
582 (st.cursor, st.anchor)
583 };
584 let new_cur = if mods.meta {
585 0 // Mac: Cmd+Left = line start
586 } else if !mods.shift && cur != anchor {
587 cur.min(anchor) // collapse to left
588 } else if word {
589 prev_word_boundary(&self.edit.borrow().text, cur)
590 } else {
591 prev_char_boundary(&self.edit.borrow().text, cur)
592 };
593 let new_anchor = if mods.shift { anchor } else { new_cur };
594 let mut st = self.edit.borrow_mut();
595 st.cursor = new_cur;
596 st.anchor = new_anchor;
597 drop(st);
598 if new_cur == 0 {
599 self.scroll_x = 0.0;
600 }
601 self.ensure_cursor_visible();
602 EventResult::Consumed
603 }
604
605 // ── Arrow Right ───────────────────────────────────────────────
606 // Symmetric with ArrowLeft. Mac: `Cmd+Right` = end of line.
607 Key::ArrowRight => {
608 self.flush_pending();
609 let text_len = self.edit.borrow().text.len();
610 let (cur, anchor) = {
611 let st = self.edit.borrow();
612 (st.cursor, st.anchor)
613 };
614 let new_cur = if mods.meta {
615 text_len // Mac: Cmd+Right = line end
616 } else if !mods.shift && cur != anchor {
617 cur.max(anchor) // collapse to right
618 } else if word {
619 next_word_boundary(&self.edit.borrow().text, cur)
620 } else if cur < text_len {
621 next_char_boundary(&self.edit.borrow().text, cur)
622 } else {
623 cur
624 };
625 let new_anchor = if mods.shift { anchor } else { new_cur };
626 let mut st = self.edit.borrow_mut();
627 st.cursor = new_cur;
628 st.anchor = new_anchor;
629 drop(st);
630 self.ensure_cursor_visible();
631 EventResult::Consumed
632 }
633
634 // ── Arrow Up / Down ──────────────────────────────────────────
635 // Single-line field, so vertical arrows only matter for the Mac
636 // `Cmd+Up` / `Cmd+Down` (start / end of document) convention —
637 // treat as Home / End. Plain arrows fall through so callers
638 // can spin numeric-input-style steppers, etc.
639 Key::ArrowUp if mods.meta => {
640 self.flush_pending();
641 let (_, anchor) = {
642 let st = self.edit.borrow();
643 (st.cursor, st.anchor)
644 };
645 let new_cur = 0;
646 let new_anchor = if mods.shift { anchor } else { new_cur };
647 let mut st = self.edit.borrow_mut();
648 st.cursor = new_cur;
649 st.anchor = new_anchor;
650 drop(st);
651 self.scroll_x = 0.0;
652 EventResult::Consumed
653 }
654 Key::ArrowDown if mods.meta => {
655 self.flush_pending();
656 let len = self.edit.borrow().text.len();
657 let (_, anchor) = {
658 let st = self.edit.borrow();
659 (st.cursor, st.anchor)
660 };
661 let new_cur = len;
662 let new_anchor = if mods.shift { anchor } else { new_cur };
663 let mut st = self.edit.borrow_mut();
664 st.cursor = new_cur;
665 st.anchor = new_anchor;
666 drop(st);
667 self.ensure_cursor_visible();
668 EventResult::Consumed
669 }
670
671 // ── Home ──────────────────────────────────────────────────────
672 // Ctrl+Home is "start of document" on Windows — for a single-
673 // line field that's the same as plain Home; accept both.
674 Key::Home => {
675 self.flush_pending();
676 let mut st = self.edit.borrow_mut();
677 st.cursor = 0;
678 if !mods.shift {
679 st.anchor = 0;
680 }
681 drop(st);
682 self.scroll_x = 0.0;
683 EventResult::Consumed
684 }
685
686 // ── End ───────────────────────────────────────────────────────
687 // Ctrl+End analogous to Ctrl+Home — treated as plain End here.
688 Key::End => {
689 self.flush_pending();
690 let len = self.edit.borrow().text.len();
691 let mut st = self.edit.borrow_mut();
692 st.cursor = len;
693 if !mods.shift {
694 st.anchor = len;
695 }
696 drop(st);
697 self.ensure_cursor_visible();
698 EventResult::Consumed
699 }
700
701 // ── Enter ─────────────────────────────────────────────────────
702 // Commit as edit-complete too so numeric/parsed fields apply the
703 // value on Enter (not only on blur). Snapshot text to prevent a
704 // second edit-complete firing on later focus loss.
705 Key::Enter => {
706 self.flush_pending();
707 self.notify_enter();
708 if self.text() != self.text_on_focus {
709 self.notify_edit_complete();
710 self.text_on_focus = self.text();
711 }
712 EventResult::Consumed
713 }
714
715 // Escape: clear selection if any, else let the parent
716 // (typically a modal dialog) handle it.
717 Key::Escape => {
718 self.flush_pending();
719 let (cur, anc) = {
720 let st = self.edit.borrow();
721 (st.cursor, st.anchor)
722 };
723 if cur != anc {
724 self.edit.borrow_mut().anchor = cur;
725 EventResult::Consumed
726 } else {
727 EventResult::Ignored
728 }
729 }
730
731 _ => {
732 let _ = anchor_before;
733 EventResult::Ignored
734 }
735 }
736 }
737}
738
739// ---------------------------------------------------------------------------
740// Widget impl
741// ---------------------------------------------------------------------------