1use std::rc::Rc;
20use std::cell::RefCell;
21use std::sync::Arc;
22
23use web_time::Instant;
26
27use crate::event::{Event, EventResult, Key, Modifiers, MouseButton};
28use crate::geometry::{Rect, Size};
29use crate::draw_ctx::DrawCtx;
30use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
31use crate::text::{Font, measure_advance};
32use crate::undo::UndoBuffer;
33use crate::widget::{BackbufferCache, BackbufferMode, Widget};
34use super::text_field_core::{
35 TextEditCommand, TextEditState,
36 byte_at_x, next_char_boundary, next_word_boundary,
37 prev_char_boundary, prev_word_boundary, word_range_at,
38};
39
40#[cfg(feature = "clipboard")]
45fn clipboard_get() -> Option<String> {
46 arboard::Clipboard::new().ok()?.get_text().ok()
47}
48#[cfg(all(not(feature = "clipboard"), not(target_arch = "wasm32")))]
50fn clipboard_get() -> Option<String> { None }
51#[cfg(all(not(feature = "clipboard"), target_arch = "wasm32"))]
53fn clipboard_get() -> Option<String> { crate::wasm_clipboard::get() }
54
55#[cfg(feature = "clipboard")]
56fn clipboard_set(text: &str) {
57 if let Ok(mut cb) = arboard::Clipboard::new() { let _ = cb.set_text(text.to_string()); }
58}
59#[cfg(all(not(feature = "clipboard"), not(target_arch = "wasm32")))]
61fn clipboard_set(_: &str) {}
62#[cfg(all(not(feature = "clipboard"), target_arch = "wasm32"))]
65fn clipboard_set(text: &str) { crate::wasm_clipboard::set(text); }
66
67pub struct TextField {
73 bounds: Rect,
74 children: Vec<Box<dyn Widget>>,
75 base: WidgetBase,
76
77 edit: Rc<RefCell<TextEditState>>,
79
80 undo: UndoBuffer,
82
83 pending_insert: Option<TextEditCommand>,
85
86 text_on_focus: String,
88
89 font: Arc<Font>,
91 font_size: f64,
92
93 pub read_only: bool,
95 pub select_all_on_focus: bool,
96 pub password_mode: bool,
99
100 focused: bool,
102 hovered: bool,
103 mouse_down: bool,
104 scroll_x: f64,
105
106 focus_time: Option<Instant>,
108 blink_last_phase: std::cell::Cell<u64>,
115
116 last_click_time: Option<Instant>,
118
119 pub placeholder: String,
121
122 pub padding: f64,
124
125 on_change: Option<Box<dyn FnMut(&str)>>,
127 on_enter: Option<Box<dyn FnMut(&str)>>,
128 on_edit_complete: Option<Box<dyn FnMut(&str)>>,
129
130 cache: BackbufferCache,
137 last_sig: Option<TextFieldSig>,
138}
139
140#[derive(Clone, PartialEq)]
141struct TextFieldSig {
142 text: String,
143 cursor: usize,
144 anchor: usize,
145 focused: bool,
146 hovered: bool,
147 scroll_x_bits: u64,
148 w_bits: u64,
149 h_bits: u64,
150 font_ptr: usize,
157 font_size_bits: u64,
158}
159
160impl TextField {
161 pub fn new(font: Arc<Font>) -> Self {
162 Self {
163 bounds: Rect::default(),
164 children: Vec::new(),
165 base: WidgetBase::new(),
166 edit: Rc::new(RefCell::new(TextEditState::default())),
167 undo: UndoBuffer::new(),
168 pending_insert: None,
169 text_on_focus: String::new(),
170 font,
171 font_size: 14.0,
172 read_only: false,
173 select_all_on_focus: false,
174 password_mode: false,
175 focused: false,
176 hovered: false,
177 mouse_down: false,
178 scroll_x: 0.0,
179 focus_time: None,
180 blink_last_phase: std::cell::Cell::new(u64::MAX),
181 last_click_time: None,
182 placeholder: String::new(),
183 padding: 8.0,
184 on_change: None,
185 on_enter: None,
186 on_edit_complete: None,
187 cache: BackbufferCache::default(),
188 last_sig: None,
189 }
190 }
191
192 fn active_font(&self) -> Arc<Font> {
197 crate::font_settings::current_system_font()
198 .unwrap_or_else(|| Arc::clone(&self.font))
199 }
200
201 pub fn with_font_size(mut self, s: f64) -> Self { self.font_size = s; self }
204 pub fn with_padding(mut self, p: f64) -> Self { self.padding = p; self }
205 pub fn with_read_only(mut self, v: bool) -> Self { self.read_only = v; self }
206 pub fn with_select_all_on_focus(mut self, v: bool) -> Self { self.select_all_on_focus = v; self }
207 pub fn with_password_mode(mut self, v: bool) -> Self { self.password_mode = v; self }
208
209 pub fn with_placeholder(mut self, s: impl Into<String>) -> Self { self.placeholder = s.into(); self }
210 pub fn with_text(self, s: impl Into<String>) -> Self {
211 let t = s.into();
212 let len = t.len();
213 let mut st = self.edit.borrow_mut();
214 st.text = t;
215 st.cursor = len;
216 st.anchor = len;
217 drop(st);
218 self
219 }
220
221 pub fn on_change(mut self, cb: impl FnMut(&str) + 'static) -> Self {
222 self.on_change = Some(Box::new(cb)); self
223 }
224 pub fn on_enter(mut self, cb: impl FnMut(&str) + 'static) -> Self {
225 self.on_enter = Some(Box::new(cb)); self
226 }
227 pub fn on_edit_complete(mut self, cb: impl FnMut(&str) + 'static) -> Self {
228 self.on_edit_complete = Some(Box::new(cb)); self
229 }
230
231 pub fn with_margin(mut self, m: Insets) -> Self { self.base.margin = m; self }
232 pub fn with_h_anchor(mut self, h: HAnchor) -> Self { self.base.h_anchor = h; self }
233 pub fn with_v_anchor(mut self, v: VAnchor) -> Self { self.base.v_anchor = v; self }
234 pub fn with_min_size(mut self, s: Size) -> Self { self.base.min_size = s; self }
235 pub fn with_max_size(mut self, s: Size) -> Self { self.base.max_size = s; self }
236
237 pub fn text(&self) -> String { self.edit.borrow().text.clone() }
240 pub fn cursor_pos(&self) -> usize { self.edit.borrow().cursor }
241 pub fn selection(&self) -> String {
242 let st = self.edit.borrow();
243 let lo = st.cursor.min(st.anchor);
244 let hi = st.cursor.max(st.anchor);
245 st.text[lo..hi].to_string()
246 }
247
248 pub fn set_text(&mut self, s: impl Into<String>) {
249 let t = s.into();
250 let len = t.len();
251 let mut st = self.edit.borrow_mut();
252 st.text = t;
253 st.cursor = len;
254 st.anchor = len;
255 drop(st);
256 self.undo.clear_history();
257 self.pending_insert = None;
258 }
259
260 fn snap(&self) -> TextEditState { self.edit.borrow().clone() }
263 #[allow(dead_code)]
264 fn apply(&self, s: TextEditState) { *self.edit.borrow_mut() = s; }
265
266 #[allow(dead_code)]
267 fn sel_min(&self) -> usize { let s = self.edit.borrow(); s.cursor.min(s.anchor) }
268 #[allow(dead_code)]
269 fn sel_max(&self) -> usize { let s = self.edit.borrow(); s.cursor.max(s.anchor) }
270 fn has_selection(&self) -> bool { let s = self.edit.borrow(); s.cursor != s.anchor }
271
272 fn flush_pending(&mut self) {
274 if let Some(cmd) = self.pending_insert.take() {
275 self.undo.add(Box::new(cmd));
276 }
277 }
278
279 fn click_to_cursor(&self, real_text: &str, tx: f64) -> usize {
282 let font = self.active_font();
283 if self.password_mode {
284 const BULLET: char = '•';
285 const BULLET_LEN: usize = 3;
286 let n = real_text.chars().count();
287 let masked = BULLET.to_string().repeat(n);
288 let disp = byte_at_x(&font, &masked, self.font_size, tx);
289 let char_idx = disp / BULLET_LEN;
291 real_text.char_indices()
292 .nth(char_idx)
293 .map(|(i, _)| i)
294 .unwrap_or(real_text.len())
295 } else {
296 byte_at_x(&font, real_text, self.font_size, tx)
297 }
298 }
299
300 fn ensure_cursor_visible(&mut self) {
302 if self.bounds.width < 1.0 { return; }
303 let inner_w = (self.bounds.width - self.padding * 2.0).max(0.0);
304 let font = self.active_font();
305 let cx = {
306 let st = self.edit.borrow();
307 if self.password_mode {
308 const BULLET: char = '•';
309 #[allow(dead_code)]
310 const BULLET_LEN: usize = 3;
311 let n = st.text[..st.cursor].chars().count();
312 let masked = BULLET.to_string().repeat(n);
313 measure_advance(&font, &masked, self.font_size)
314 } else {
315 measure_advance(&font, &st.text[..st.cursor], self.font_size)
316 }
317 };
318 if cx < self.scroll_x { self.scroll_x = cx; }
319 else if cx > self.scroll_x + inner_w { self.scroll_x = cx - inner_w; }
320 }
321
322 fn do_insert(&mut self, s: &str, is_single_char: bool) {
327 let before = self.snap();
328 let had_selection = before.cursor != before.anchor;
329
330 {
332 let mut st = self.edit.borrow_mut();
333 if st.cursor != st.anchor {
334 let lo = st.cursor.min(st.anchor);
335 let hi = st.cursor.max(st.anchor);
336 st.text.drain(lo..hi);
337 st.cursor = lo;
338 st.anchor = lo;
339 }
340 let cursor = st.cursor;
341 st.text.insert_str(cursor, s);
342 st.cursor = cursor + s.len();
343 st.anchor = st.cursor;
344 }
345
346 let after = self.snap();
347
348 if is_single_char && !had_selection {
349 if let Some(ref mut pending) = self.pending_insert {
351 pending.after = after;
352 } else {
353 self.pending_insert = Some(TextEditCommand {
354 name: "insert text",
355 before,
356 after,
357 target: Rc::clone(&self.edit),
358 });
359 }
360 } else {
361 self.flush_pending();
363 self.undo.add(Box::new(TextEditCommand {
364 name: "insert text",
365 before,
366 after,
367 target: Rc::clone(&self.edit),
368 }));
369 }
370
371 self.ensure_cursor_visible();
372 self.notify_change();
373 }
374
375 fn do_delete(&mut self, forward: bool, word: bool) {
377 self.flush_pending();
378 let before = self.snap();
379 {
380 let mut st = self.edit.borrow_mut();
381 if st.cursor != st.anchor {
382 let lo = st.cursor.min(st.anchor);
383 let hi = st.cursor.max(st.anchor);
384 st.text.drain(lo..hi);
385 st.cursor = lo;
386 st.anchor = lo;
387 } else if forward {
388 let cursor = st.cursor;
389 let end = if word { next_word_boundary(&st.text, cursor) }
390 else { next_char_boundary(&st.text, cursor) };
391 if end > cursor { st.text.drain(cursor..end); }
392 st.anchor = st.cursor;
393 } else {
394 let cursor = st.cursor;
395 let start = if word { prev_word_boundary(&st.text, cursor) }
396 else { prev_char_boundary(&st.text, cursor) };
397 if start < cursor {
398 st.text.drain(start..cursor);
399 st.cursor = start;
400 st.anchor = start;
401 }
402 }
403 }
404 let after = self.snap();
405 self.undo.add(Box::new(TextEditCommand {
406 name: "delete text", before, after, target: Rc::clone(&self.edit),
407 }));
408 self.ensure_cursor_visible();
409 self.notify_change();
410 }
411
412 fn do_undo(&mut self) {
413 self.flush_pending();
414 self.undo.undo();
415 let len = self.edit.borrow().text.len();
417 let mut st = self.edit.borrow_mut();
418 st.cursor = st.cursor.min(len);
419 st.anchor = st.anchor.min(len);
420 drop(st);
421 self.ensure_cursor_visible();
422 self.notify_change();
423 }
424
425 fn do_redo(&mut self) {
426 self.flush_pending();
427 self.undo.redo();
428 let len = self.edit.borrow().text.len();
429 let mut st = self.edit.borrow_mut();
430 st.cursor = st.cursor.min(len);
431 st.anchor = st.anchor.min(len);
432 drop(st);
433 self.ensure_cursor_visible();
434 self.notify_change();
435 }
436
437 fn notify_change(&mut self) {
440 if let Some(mut cb) = self.on_change.take() {
441 let t = self.text(); cb(&t); self.on_change = Some(cb);
442 }
443 }
444 fn notify_enter(&mut self) {
445 if let Some(mut cb) = self.on_enter.take() {
446 let t = self.text(); cb(&t); self.on_enter = Some(cb);
447 }
448 }
449 fn notify_edit_complete(&mut self) {
450 if let Some(mut cb) = self.on_edit_complete.take() {
451 let t = self.text(); cb(&t); self.on_edit_complete = Some(cb);
452 }
453 }
454
455 fn handle_key(&mut self, key: &Key, mods: Modifiers) -> EventResult {
458 let anchor_before = self.edit.borrow().anchor;
460
461 let cmd = mods.ctrl || mods.meta;
465 let word = mods.ctrl || mods.alt;
469
470 match key {
471 Key::Char(c) if !self.read_only || cmd => {
473 if cmd {
474 return match c {
475 'a' | 'A' => {
476 let len = self.edit.borrow().text.len();
477 let mut st = self.edit.borrow_mut();
478 st.anchor = 0; st.cursor = len;
479 EventResult::Consumed
480 }
481 'z' | 'Z' if !mods.shift => { self.do_undo(); EventResult::Consumed }
482 'z' | 'Z' | 'y' | 'Y' => { self.do_redo(); EventResult::Consumed }
483 'x' | 'X' => {
484 if self.has_selection() {
485 clipboard_set(&self.selection());
486 self.do_delete(false, false); }
488 EventResult::Consumed
489 }
490 'c' | 'C' => {
491 if self.has_selection() { clipboard_set(&self.selection()); }
492 EventResult::Consumed
493 }
494 'v' | 'V' => {
495 if let Some(clip) = clipboard_get() { self.do_insert(&clip, false); }
496 EventResult::Consumed
497 }
498 _ => EventResult::Ignored,
499 };
500 }
501 if self.read_only { return EventResult::Ignored; }
502 let mut buf = [0u8; 4];
503 let s = c.encode_utf8(&mut buf);
504 self.do_insert(s, true);
505 EventResult::Consumed
506 }
507
508 Key::Insert => {
515 if mods.shift && !self.read_only {
516 if let Some(clip) = clipboard_get() { self.do_insert(&clip, false); }
517 return EventResult::Consumed;
518 }
519 if cmd {
520 if self.has_selection() { clipboard_set(&self.selection()); }
521 return EventResult::Consumed;
522 }
523 EventResult::Ignored
524 }
525
526 Key::Backspace if !self.read_only => {
528 self.do_delete(false, word);
529 EventResult::Consumed
530 }
531
532 Key::Delete if !self.read_only => {
534 if mods.shift {
535 if self.has_selection() { clipboard_set(&self.selection()); self.do_delete(false, false); }
537 } else {
538 self.do_delete(true, word);
539 }
540 EventResult::Consumed
541 }
542
543 Key::ArrowLeft => {
548 self.flush_pending();
549 let (cur, anchor) = {
550 let st = self.edit.borrow();
551 (st.cursor, st.anchor)
552 };
553 let new_cur = if mods.meta {
554 0 } else if !mods.shift && cur != anchor {
556 cur.min(anchor) } else if word {
558 prev_word_boundary(&self.edit.borrow().text, cur)
559 } else {
560 prev_char_boundary(&self.edit.borrow().text, cur)
561 };
562 let new_anchor = if mods.shift { anchor } else { new_cur };
563 let mut st = self.edit.borrow_mut();
564 st.cursor = new_cur; st.anchor = new_anchor;
565 drop(st);
566 if new_cur == 0 { self.scroll_x = 0.0; }
567 self.ensure_cursor_visible();
568 EventResult::Consumed
569 }
570
571 Key::ArrowRight => {
574 self.flush_pending();
575 let text_len = self.edit.borrow().text.len();
576 let (cur, anchor) = {
577 let st = self.edit.borrow();
578 (st.cursor, st.anchor)
579 };
580 let new_cur = if mods.meta {
581 text_len } else if !mods.shift && cur != anchor {
583 cur.max(anchor) } else if word {
585 next_word_boundary(&self.edit.borrow().text, cur)
586 } else if cur < text_len {
587 next_char_boundary(&self.edit.borrow().text, cur)
588 } else {
589 cur
590 };
591 let new_anchor = if mods.shift { anchor } else { new_cur };
592 let mut st = self.edit.borrow_mut();
593 st.cursor = new_cur; st.anchor = new_anchor;
594 drop(st);
595 self.ensure_cursor_visible();
596 EventResult::Consumed
597 }
598
599 Key::ArrowUp if mods.meta => {
605 self.flush_pending();
606 let (_, anchor) = { let st = self.edit.borrow(); (st.cursor, st.anchor) };
607 let new_cur = 0;
608 let new_anchor = if mods.shift { anchor } else { new_cur };
609 let mut st = self.edit.borrow_mut();
610 st.cursor = new_cur; st.anchor = new_anchor;
611 drop(st);
612 self.scroll_x = 0.0;
613 EventResult::Consumed
614 }
615 Key::ArrowDown if mods.meta => {
616 self.flush_pending();
617 let len = self.edit.borrow().text.len();
618 let (_, anchor) = { let st = self.edit.borrow(); (st.cursor, st.anchor) };
619 let new_cur = len;
620 let new_anchor = if mods.shift { anchor } else { new_cur };
621 let mut st = self.edit.borrow_mut();
622 st.cursor = new_cur; st.anchor = new_anchor;
623 drop(st);
624 self.ensure_cursor_visible();
625 EventResult::Consumed
626 }
627
628 Key::Home => {
632 self.flush_pending();
633 let mut st = self.edit.borrow_mut();
634 st.cursor = 0;
635 if !mods.shift { st.anchor = 0; }
636 drop(st);
637 self.scroll_x = 0.0;
638 EventResult::Consumed
639 }
640
641 Key::End => {
644 self.flush_pending();
645 let len = self.edit.borrow().text.len();
646 let mut st = self.edit.borrow_mut();
647 st.cursor = len;
648 if !mods.shift { st.anchor = len; }
649 drop(st);
650 self.ensure_cursor_visible();
651 EventResult::Consumed
652 }
653
654 Key::Enter => {
659 self.flush_pending();
660 self.notify_enter();
661 if self.text() != self.text_on_focus {
662 self.notify_edit_complete();
663 self.text_on_focus = self.text();
664 }
665 EventResult::Consumed
666 }
667
668 Key::Escape => {
670 self.flush_pending();
671 let cur = self.edit.borrow().cursor;
672 self.edit.borrow_mut().anchor = cur;
673 EventResult::Consumed
674 }
675
676 _ => { let _ = anchor_before; EventResult::Ignored }
677 }
678 }
679}
680
681impl Widget for TextField {
686 fn type_name(&self) -> &'static str { "TextField" }
687 fn bounds(&self) -> Rect { self.bounds }
688 fn set_bounds(&mut self, b: Rect) { self.bounds = b; }
689 fn children(&self) -> &[Box<dyn Widget>] { &self.children }
690 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>> { &mut self.children }
691 fn is_focusable(&self) -> bool { true }
692
693 fn needs_paint(&self) -> bool {
704 if !self.focused { return false; }
705 let Some(t) = self.focus_time else { return false; };
706 let current_phase = (t.elapsed().as_millis() / 500) as u64;
707 current_phase != self.blink_last_phase.get()
708 }
709
710 fn next_paint_deadline(&self) -> Option<web_time::Instant> {
711 if !self.focused { return None; }
712 let t = self.focus_time?;
713 let ms = t.elapsed().as_millis() as u64;
714 let next_phase = (ms / 500) + 1;
715 Some(t + std::time::Duration::from_millis(next_phase * 500))
716 }
717
718 fn margin(&self) -> Insets { self.base.margin }
719 fn h_anchor(&self) -> HAnchor { self.base.h_anchor }
720 fn v_anchor(&self) -> VAnchor { self.base.v_anchor }
721 fn min_size(&self) -> Size { self.base.min_size }
722 fn max_size(&self) -> Size { self.base.max_size }
723
724 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
725 Some(&mut self.cache)
726 }
727
728 fn backbuffer_mode(&self) -> BackbufferMode {
729 if crate::font_settings::lcd_enabled() {
730 BackbufferMode::LcdCoverage
731 } else {
732 BackbufferMode::Rgba
733 }
734 }
735
736 fn layout(&mut self, available: Size) -> Size {
737 let st = self.edit.borrow();
741 let font = self.active_font();
742 let sig = TextFieldSig {
743 text: st.text.clone(),
744 cursor: st.cursor,
745 anchor: st.anchor,
746 focused: self.focused,
747 hovered: self.hovered,
748 scroll_x_bits: self.scroll_x.to_bits(),
749 w_bits: self.bounds.width .to_bits(),
750 h_bits: self.bounds.height.to_bits(),
751 font_ptr: Arc::as_ptr(&font) as usize,
752 font_size_bits: self.font_size.to_bits(),
753 };
754 drop(st);
755 if self.last_sig.as_ref() != Some(&sig) {
756 self.last_sig = Some(sig);
757 self.cache.invalidate();
758 }
759 Size::new(available.width, (self.font_size * 2.4).max(28.0))
760 }
761
762 fn paint(&mut self, ctx: &mut dyn DrawCtx) {
763 let w = self.bounds.width;
764 let h = self.bounds.height;
765 let r = 6.0;
766 let pad = self.padding;
767 let (raw_text, raw_cursor, raw_anchor) = {
768 let st = self.edit.borrow();
769 (st.text.clone(), st.cursor, st.anchor)
770 };
771 let (text, cursor, anchor) = if self.password_mode {
774 const BULLET: char = '•';
775 const BULLET_LEN: usize = 3; let n = raw_text.chars().count();
777 let masked = BULLET.to_string().repeat(n);
778 let cur = raw_text[..raw_cursor].chars().count() * BULLET_LEN;
779 let anc = raw_text[..raw_anchor].chars().count() * BULLET_LEN;
780 (masked, cur, anc)
781 } else {
782 (raw_text, raw_cursor, raw_anchor)
783 };
784
785 let v = ctx.visuals();
786
787 ctx.set_fill_color(v.widget_bg);
789 ctx.begin_path();
790 ctx.rounded_rect(0.0, 0.0, w, h, r);
791 ctx.fill();
792
793 ctx.clip_rect(pad, 0.0, (w - pad * 2.0).max(0.0), h);
795
796 let font = self.active_font();
797 ctx.set_font(Arc::clone(&font));
798 ctx.set_font_size(self.font_size);
799
800 let m = ctx.measure_text("Ag").unwrap_or_default();
801 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
802 let text_x = pad - self.scroll_x;
803
804 if cursor != anchor {
806 let lo = cursor.min(anchor);
807 let hi = cursor.max(anchor);
808 let lo_x = measure_advance(&font, &text[..lo], self.font_size);
809 let hi_x = measure_advance(&font, &text[..hi], self.font_size);
810 let sx = (text_x + lo_x).max(pad);
811 let sw = (text_x + hi_x).min(w - pad) - sx;
812 if sw > 0.0 {
813 let hl_bot = baseline_y - m.descent;
814 let hl_h = (m.ascent + m.descent) * 1.2;
815 ctx.set_fill_color(if self.focused {
816 v.selection_bg
817 } else {
818 v.selection_bg_unfocused
819 });
820 ctx.begin_path();
821 ctx.rect(sx, hl_bot - hl_h * 0.1, sw, hl_h);
822 ctx.fill();
823 }
824 }
825
826 if text.is_empty() && !self.focused {
828 ctx.set_fill_color(v.text_dim);
829 ctx.fill_text(&self.placeholder, text_x, baseline_y);
830 } else {
831 ctx.set_fill_color(v.text_color);
832 ctx.fill_text(&text, text_x, baseline_y);
833 }
834
835 ctx.reset_clip();
839
840 let border_color = if self.focused { v.accent }
842 else if self.hovered { v.widget_stroke_active }
843 else { v.widget_stroke };
844 ctx.set_stroke_color(border_color);
845 ctx.set_line_width(if self.focused { 2.0 } else { 1.0 });
846 ctx.begin_path();
847 ctx.rounded_rect(0.0, 0.0, w, h, r);
848 ctx.stroke();
849 }
850
851 fn paint_overlay(&mut self, ctx: &mut dyn DrawCtx) {
856 if self.focused {
861 if let Some(t) = self.focus_time {
862 let phase = (t.elapsed().as_millis() / 500) as u64;
863 self.blink_last_phase.set(phase);
864 }
865 }
866
867 let cursor_visible = self.focused && {
868 let st = self.edit.borrow();
869 st.cursor == st.anchor
870 } && match self.focus_time {
871 Some(t) => (t.elapsed().as_millis() / 500) % 2 == 0,
872 None => false,
873 };
874 if !cursor_visible { return; }
875
876 let (text, cursor) = {
877 let st = self.edit.borrow();
878 let text = if self.password_mode {
879 const BULLET: char = '•';
880 let n = st.text.chars().count();
881 BULLET.to_string().repeat(n)
882 } else {
883 st.text.clone()
884 };
885 let cursor = if self.password_mode {
886 const BULLET_LEN: usize = 3;
887 st.text[..st.cursor].chars().count() * BULLET_LEN
888 } else {
889 st.cursor
890 };
891 (text, cursor)
892 };
893
894 let h = self.bounds.height;
895 let pad = self.padding;
896 let v = ctx.visuals();
897
898 let font = self.active_font();
899 ctx.set_font(Arc::clone(&font));
900 ctx.set_font_size(self.font_size);
901 let m = ctx.measure_text("Ag").unwrap_or_default();
902 let baseline_y = h * 0.5 - (m.ascent - m.descent) * 0.5;
903 let text_x = pad - self.scroll_x;
904 let cx = text_x + measure_advance(&font, &text[..cursor], self.font_size);
905 let top = baseline_y + m.ascent;
906 let bot = baseline_y - m.descent;
907
908 ctx.save();
911 ctx.clip_rect(pad, 0.0, (self.bounds.width - pad * 2.0).max(0.0), h);
912 ctx.set_stroke_color(v.accent);
913 ctx.set_line_width(1.5);
914 ctx.begin_path();
915 ctx.move_to(cx, bot);
916 ctx.line_to(cx, top);
917 ctx.stroke();
918 ctx.restore();
919 }
920
921 fn on_event(&mut self, event: &Event) -> EventResult {
922 match event {
923 Event::MouseMove { pos } => {
924 let was = self.hovered;
925 self.hovered = self.hit_test(*pos);
926 if self.mouse_down && self.focused {
927 let tx = pos.x - self.padding + self.scroll_x;
928 let text = self.edit.borrow().text.clone();
929 let new_cur = self.click_to_cursor(&text, tx);
930 self.edit.borrow_mut().cursor = new_cur;
931 crate::animation::request_tick();
932 }
933 if was != self.hovered { crate::animation::request_tick(); }
934 EventResult::Ignored
935 }
936
937 Event::MouseDown { pos, button: MouseButton::Left, modifiers: mods } => {
938 self.mouse_down = true;
939 let tx = pos.x - self.padding + self.scroll_x;
940 let text = self.edit.borrow().text.clone();
941 let new_cur = self.click_to_cursor(&text, tx);
942
943 let is_double = self.last_click_time
945 .map(|t| t.elapsed().as_millis() < 350)
946 .unwrap_or(false);
947 self.last_click_time = Some(Instant::now());
948
949 if is_double && !mods.shift {
950 let (ws, we) = word_range_at(&text, new_cur);
951 self.edit.borrow_mut().anchor = ws;
952 self.edit.borrow_mut().cursor = we;
953 } else if mods.shift {
954 self.edit.borrow_mut().cursor = new_cur;
955 } else {
956 self.edit.borrow_mut().cursor = new_cur;
957 self.edit.borrow_mut().anchor = new_cur;
958 }
959 self.focus_time = Some(Instant::now());
961 crate::animation::request_tick();
962 EventResult::Consumed
963 }
964
965 Event::MouseUp { button: MouseButton::Left, .. } => {
966 self.mouse_down = false;
967 EventResult::Ignored
968 }
969
970 Event::FocusGained => {
971 self.focused = true;
972 self.focus_time = Some(Instant::now());
973 self.text_on_focus = self.text();
974 if self.select_all_on_focus {
975 let len = self.edit.borrow().text.len();
976 self.edit.borrow_mut().anchor = 0;
977 self.edit.borrow_mut().cursor = len;
978 }
979 crate::animation::request_tick();
980 EventResult::Ignored
981 }
982
983 Event::FocusLost => {
984 let was_focused = self.focused;
985 self.focused = false;
986 self.focus_time = None;
987 self.mouse_down = false;
988 self.flush_pending();
989 if self.text() != self.text_on_focus { self.notify_edit_complete(); }
990 if was_focused { crate::animation::request_tick(); }
991 EventResult::Ignored
992 }
993
994 Event::KeyDown { key, modifiers } if self.focused => {
995 self.focus_time = Some(Instant::now());
997 let result = self.handle_key(key, *modifiers);
998 if result == EventResult::Consumed {
1001 crate::animation::request_tick();
1002 }
1003 result
1004 }
1005
1006 _ => EventResult::Ignored,
1007 }
1008 }
1009}