Skip to main content

vizia_core/views/
textbox.rs

1use std::cell::RefCell;
2use std::ops::Range;
3use std::rc::Rc;
4
5use crate::prelude::*;
6
7use crate::text::{
8    Direction, EditableText, Movement, PreeditBackup, Selection, VerticalMovement, apply_movement,
9    enforce_text_bounds, ensure_visible, offset_for_delete_backwards, resolved_text_direction,
10};
11use accesskit::{ActionData, ActionRequest, TextDirection, TextPosition, TextSelection};
12use skia_safe::textlayout::{RectHeightStyle, RectWidthStyle};
13use skia_safe::{ClipOp, Paint, PaintStyle, Point, Rect};
14use unicode_segmentation::UnicodeSegmentation;
15
16/// Events for modifying a textbox.
17pub enum TextEvent {
18    /// Insert a string of text into the textbox.
19    InsertText(String),
20    /// Update the preedit text of the textbox (for IME input).
21    UpdatePreedit(String, Option<(usize, usize)>),
22    /// Clear the preedit text of the textbox.
23    ClearPreedit,
24    /// Reset the text of the textbox to the bound data.
25    Clear,
26    /// Delete a section of text, determined by the `Movement`.
27    DeleteText(Movement),
28    /// Move the cursor and selection.
29    MoveCursor(Movement, bool),
30    /// Select all text.
31    SelectAll,
32    /// Select the word at the current cursor position.
33    SelectWord,
34    /// Select the paragraph at the current cursor position.
35    SelectParagraph,
36    /// Toggle the textbox to allow text input.
37    StartEdit,
38    /// Toggle the textbox to *not* allow text input.
39    EndEdit,
40    /// Trigger the `on_submit` callback with the current text.
41    Submit(bool),
42    /// Specify the 'hit' position of the mouse cursor.
43    Hit(f32, f32, bool),
44    /// Specify the 'drag' position of the mouse cursor.
45    Drag(f32, f32),
46    /// Specify the scroll offset of the textbox.
47    Scroll(f32, f32),
48    /// Copy the textbox buffer to the clipboard.
49    Copy,
50    /// Paste the clipboard buffer into the textbox.
51    Paste,
52    /// Cut the textbox text and place it in the clipboard.
53    Cut,
54    /// Set the placeholder text of the textbox.
55    SetPlaceholder(String),
56    /// Trigger the `on_blur` callback.
57    Blur,
58    /// Set whether masked text should be visible.
59    SetMaskVisible(bool),
60    /// Toggle whether masked text should be visible.
61    ToggleMaskVisible,
62    /// Toggle the visibility of the text Caret.
63    ToggleCaret,
64}
65
66/// The `Textbox` view provides an input control for editing a value as a string.
67///
68/// The textbox takes a lens to some value, which must be a type which can convert to and from a `String`,
69/// as determined by the `ToString` and `FromStr` traits. The value type is used for validation and returned by
70/// the `on_submit` callback, which is triggered when the textbox is submitted with the enter key or when the textbox
71/// loses keyboard focus.
72pub struct Textbox<R, T> {
73    value: R,
74    kind: TextboxKind,
75    edit: bool,
76    transform: Rc<RefCell<(f32, f32)>>,
77    on_edit: Option<Box<dyn Fn(&mut EventContext, String) + Send + Sync>>,
78    on_submit: Option<Box<dyn Fn(&mut EventContext, T, bool) + Send + Sync>>,
79    on_blur: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
80    on_cancel: Option<Box<dyn Fn(&mut EventContext) + Send + Sync>>,
81    validate: Option<Box<dyn Fn(&T) -> bool>>,
82    placeholder: Signal<String>,
83    show_placeholder: Signal<bool>,
84    show_caret: Signal<bool>,
85    mask_char: Signal<Option<char>>,
86    max_length: Signal<Option<usize>>,
87    can_copy: Signal<bool>,
88    can_paste: Signal<bool>,
89    mask_visible: bool,
90    real_text: String,
91    caret_timer: Timer,
92    selection: Selection,
93    preedit_backup: Option<PreeditBackup>,
94    text_overflow: Option<TextOverflow>,
95    edited_since_focus: bool,
96    edited_once: bool,
97}
98
99// Determines whether the enter key submits the text or inserts a new line.
100#[derive(Copy, Clone, PartialEq, Eq)]
101enum TextboxKind {
102    SingleLine,
103    MultiLineUnwrapped,
104    MultiLineWrapped,
105}
106
107impl<R, T> Textbox<R, T>
108where
109    R: Res<T> + 'static,
110    T: Clone + ToStringLocalized + std::str::FromStr + 'static,
111{
112    /// Creates a new single-line textbox.
113    ///
114    /// # Example
115    /// ```rust
116    /// # use vizia_core::prelude::*;
117    /// #
118    /// # #[derive(Lens)]
119    /// # struct AppData {
120    /// #     text: String,
121    /// # }
122    /// #
123    /// # impl Model for AppData {}
124    /// #
125    /// # let cx = &mut Context::default();
126    /// #
127    /// # AppData { text: String::from("Hello World") }.build(cx);
128    /// #
129    /// Textbox::new(cx, AppData::text);
130    /// ```
131    pub fn new(cx: &mut Context, value: R) -> Handle<Self>
132    where
133        R: Clone,
134    {
135        Self::new_core(cx, value, TextboxKind::SingleLine)
136    }
137
138    /// Creates a new multi-line textbox.
139    ///
140    /// The `wrap` parameter determines whether text which is too long for the textbox
141    /// should soft-wrap onto multiple lines. If false, then only hard-wraps from line breaks
142    /// will cause the text to span multiple lines.
143    ///
144    /// # Example
145    /// ```rust
146    /// # use vizia_core::prelude::*;
147    /// #
148    /// # #[derive(Lens)]
149    /// # struct AppData {
150    /// #     text: String,
151    /// # }
152    /// #
153    /// # impl Model for AppData {}
154    /// #
155    /// # let cx = &mut Context::default();
156    /// #
157    /// # AppData { text: String::from("Hello World") }.build(cx);
158    /// #
159    /// Textbox::new_multiline(cx, AppData::text, true);
160    /// ```
161    pub fn new_multiline(cx: &mut Context, value: R, wrap: bool) -> Handle<Self>
162    where
163        R: Clone,
164    {
165        Self::new_core(
166            cx,
167            value,
168            if wrap { TextboxKind::MultiLineWrapped } else { TextboxKind::MultiLineUnwrapped },
169        )
170    }
171
172    fn new_core(cx: &mut Context, value: R, kind: TextboxKind) -> Handle<Self>
173    where
174        R: Clone,
175    {
176        let value_text = value.clone().to_signal(cx);
177        let caret_timer = cx.environment().caret_timer;
178        let initial_text = value.get_value(cx).to_string_local(cx);
179        let show_caret = Signal::new(false);
180        let mask_char = Signal::new(None);
181        let max_length = Signal::new(None);
182        let can_copy = Signal::new(true);
183        let can_paste = Signal::new(true);
184        let placeholder = Signal::new(String::from(""));
185        let show_placeholder = Signal::new(initial_text.is_empty());
186
187        Self {
188            value: value.clone(),
189            kind,
190            edit: false,
191            transform: Rc::new(RefCell::new((0.0, 0.0))),
192            on_edit: None,
193            on_submit: None,
194            on_blur: None,
195            on_cancel: None,
196            validate: None,
197            placeholder,
198            show_placeholder,
199            show_caret,
200            mask_char,
201            max_length,
202            can_copy,
203            can_paste,
204            mask_visible: false,
205            real_text: initial_text.clone(),
206            caret_timer,
207            selection: Selection::new(0, 0),
208            preedit_backup: None,
209            text_overflow: None,
210            edited_since_focus: false,
211            edited_once: false,
212        }
213        .build(cx, move |cx| {
214            cx.add_listener(move |textbox: &mut Self, cx, event| {
215                let flag: bool = textbox.edit;
216                event.map(|window_event, meta| match window_event {
217                    WindowEvent::MouseDown(_) => {
218                        if flag && meta.origin != cx.current() && cx.hovered() != cx.current() {
219                            cx.emit(TextEvent::Blur);
220                        }
221                    }
222
223                    _ => {}
224                });
225            });
226        })
227        .toggle_class("multiline", kind == TextboxKind::MultiLineWrapped)
228        .text_wrap(kind == TextboxKind::MultiLineWrapped)
229        .navigable(true)
230        .role(if kind == TextboxKind::SingleLine {
231            Role::TextInput
232        } else {
233            Role::MultilineTextInput
234        })
235        .text_value(value.clone())
236        .toggle_class("caret", show_caret)
237        .placeholder_shown(show_placeholder)
238        .bind(value_text, move |handle| {
239            handle.bind(placeholder, move |handle| {
240                let text = value_text.get();
241                let txt = text.to_string_local(&handle);
242                let mut display_text = String::new();
243                let handle = handle.modify(|textbox| {
244                    textbox.real_text = txt.clone();
245                    textbox.show_placeholder.set_if_changed(txt.is_empty());
246                    display_text = textbox.display_text_from_real();
247                });
248                handle.text(display_text);
249            });
250        })
251    }
252
253    fn display_text_from_real(&self) -> String {
254        if self.show_placeholder.get() {
255            return self.placeholder.get().clone();
256        }
257
258        if self.mask_visible {
259            return self.real_text.clone();
260        }
261
262        let Some(mask) = self.mask_char.get() else {
263            return self.real_text.clone();
264        };
265
266        let mut masked = String::with_capacity(self.real_text.len());
267        for grapheme in self.real_text.graphemes(true) {
268            if grapheme == "\n" {
269                masked.push('\n');
270            } else {
271                masked.push(mask);
272            }
273        }
274
275        masked
276    }
277
278    fn sync_display_text(&self, cx: &mut EventContext) {
279        cx.style.text.insert(cx.current, self.display_text_from_real());
280        cx.style.needs_text_update(cx.current);
281    }
282
283    fn grapheme_index_to_byte(text: &str, idx: usize) -> usize {
284        if idx == 0 {
285            return 0;
286        }
287
288        for (i, (byte, _)) in text.grapheme_indices(true).enumerate() {
289            if i == idx {
290                return byte;
291            }
292        }
293
294        text.len()
295    }
296
297    fn byte_to_grapheme_index(text: &str, byte_offset: usize) -> usize {
298        let mut idx = 0;
299        for (byte, _) in text.grapheme_indices(true) {
300            if byte >= byte_offset {
301                break;
302            }
303            idx += 1;
304        }
305        idx
306    }
307
308    fn selection_display_to_real(
309        display_text: &str,
310        real_text: &str,
311        selection: Selection,
312    ) -> Selection {
313        let anchor_g = Self::byte_to_grapheme_index(display_text, selection.anchor);
314        let active_g = Self::byte_to_grapheme_index(display_text, selection.active);
315        let anchor = Self::grapheme_index_to_byte(real_text, anchor_g);
316        let active = Self::grapheme_index_to_byte(real_text, active_g);
317        Selection::new(anchor, active).with_h_pos(selection.h_pos)
318    }
319
320    fn remap_selection_for_display_change(&mut self, old_display: &str, new_display: &str) {
321        let anchor_g = Self::byte_to_grapheme_index(old_display, self.selection.anchor);
322        let active_g = Self::byte_to_grapheme_index(old_display, self.selection.active);
323        self.selection.anchor = Self::grapheme_index_to_byte(new_display, anchor_g);
324        self.selection.active = Self::grapheme_index_to_byte(new_display, active_g);
325    }
326
327    fn insert_text(&mut self, cx: &mut EventContext, txt: &str) {
328        if let Some(text) = cx.style.text.get_mut(cx.current) {
329            let old_display = text.clone();
330            if self.show_placeholder.get() && !txt.is_empty() {
331                text.clear();
332                self.show_placeholder.set(false);
333            }
334
335            let real_selection =
336                Self::selection_display_to_real(&old_display, &self.real_text, self.selection);
337
338            let clamped = self.clamp_insert_text(txt, real_selection.range());
339
340            text.edit(self.selection.range(), &clamped);
341            self.real_text.edit(real_selection.range(), &clamped);
342
343            // Track the caret in grapheme space so we can remap it safely after
344            // display text is regenerated (for example when masking is enabled).
345            let new_caret_grapheme =
346                Self::byte_to_grapheme_index(text, self.selection.min() + clamped.len());
347
348            self.show_placeholder.set(self.real_text.is_empty());
349            self.sync_display_text(cx);
350            if let Some(new_display) = cx.style.text.get(cx.current) {
351                let new_caret = Self::grapheme_index_to_byte(new_display, new_caret_grapheme);
352                self.selection = Selection::caret(new_caret);
353            }
354            cx.style.needs_access_update(cx.current);
355        }
356    }
357
358    fn update_preedit(
359        &mut self,
360        cx: &mut EventContext,
361        preedit_txt: &str,
362        cursor: Option<(usize, usize)>,
363    ) {
364        if preedit_txt.is_empty() || cursor.is_none() {
365            return;
366        }
367
368        if let Some(text) = cx.style.text.get_mut(cx.current) {
369            let old_display = text.clone();
370            if self.show_placeholder.get() {
371                text.clear();
372                self.show_placeholder.set(false);
373            }
374
375            if !self.selection.is_caret() {
376                let start = self.selection.min();
377                let end = self.selection.max();
378                let real_selection =
379                    Self::selection_display_to_real(&old_display, &self.real_text, self.selection);
380
381                if end > start && end <= text.len() {
382                    text.replace_range(start..end, "");
383                }
384                if real_selection.max() <= self.real_text.len() {
385                    self.real_text.replace_range(real_selection.range(), "");
386                }
387                self.selection = Selection::caret(start);
388            }
389
390            let (original_selection, prev_preedit_text) = {
391                let preedit_backup = self
392                    .preedit_backup
393                    .get_or_insert_with(|| PreeditBackup::new(String::new(), self.selection));
394                (preedit_backup.original_selection, preedit_backup.prev_preedit.clone())
395            };
396
397            let new_caret_grapheme;
398
399            if prev_preedit_text == preedit_txt {
400                // Move the cursor only
401                let caret = original_selection.min() + cursor.unwrap().0;
402                new_caret_grapheme = Self::byte_to_grapheme_index(text, caret);
403            } else {
404                // Bytes index
405                let start = original_selection.min();
406                let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
407
408                // Delete old preedit text
409                if end > start && end <= text.len() {
410                    text.replace_range(start..end, "");
411                }
412
413                let original_real_selection = Self::selection_display_to_real(
414                    &old_display,
415                    &self.real_text,
416                    original_selection,
417                );
418                let real_start = original_real_selection.min();
419                let real_end =
420                    real_start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
421                let clamped_preedit = self.clamp_insert_text(preedit_txt, real_start..real_end);
422                if real_end > real_start && real_end <= self.real_text.len() {
423                    self.real_text.replace_range(real_start..real_end, "");
424                }
425                self.real_text.insert_str(real_start, &clamped_preedit);
426
427                text.insert_str(start, &clamped_preedit);
428
429                if let Some((cursor_index, _)) = cursor {
430                    let new_caret =
431                        original_selection.min() + cursor_index.min(clamped_preedit.len());
432                    new_caret_grapheme = Self::byte_to_grapheme_index(text, new_caret);
433                } else {
434                    // If there is no valid cursor, the default behavior is to move to the end of the text.
435                    let new_caret = original_selection.min() + clamped_preedit.chars().count();
436                    new_caret_grapheme = Self::byte_to_grapheme_index(text, new_caret);
437                }
438
439                self.preedit_backup.as_mut().unwrap().set_prev_preedit(clamped_preedit);
440            }
441
442            self.show_placeholder.set(self.real_text.is_empty());
443            self.sync_display_text(cx);
444            if let Some(new_display) = cx.style.text.get(cx.current) {
445                let new_caret = Self::grapheme_index_to_byte(new_display, new_caret_grapheme);
446                self.selection = Selection::caret(new_caret);
447            }
448        }
449    }
450
451    fn clear_preedit(&mut self, cx: &mut EventContext) {
452        if let Some(text) = cx.style.text.get_mut(cx.current) {
453            if let Some(preedit_backup) = self.preedit_backup.as_ref() {
454                let old_display = text.clone();
455                let original_selection = preedit_backup.original_selection;
456                let prev_preedit_text = preedit_backup.prev_preedit.clone();
457
458                let start = original_selection.min();
459                let end = start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
460
461                text.replace_range(start..end, "");
462
463                let original_real_selection = Self::selection_display_to_real(
464                    &old_display,
465                    &self.real_text,
466                    original_selection,
467                );
468                let real_start = original_real_selection.min();
469                let real_end =
470                    real_start + prev_preedit_text.chars().map(|c| c.len_utf8()).sum::<usize>();
471                if real_end <= self.real_text.len() {
472                    self.real_text.replace_range(real_start..real_end, "");
473                }
474
475                self.selection = original_selection;
476
477                self.preedit_backup = None;
478                self.show_placeholder.set(self.real_text.is_empty());
479                self.sync_display_text(cx);
480            }
481        }
482    }
483
484    fn delete_text(&mut self, cx: &mut EventContext, movement: Movement) {
485        if self.show_placeholder.get() {
486            return;
487        }
488
489        if self.preedit_backup.is_some() {
490            return;
491        }
492
493        if self.selection.is_caret() {
494            if movement == Movement::Grapheme(Direction::Upstream) {
495                if self.selection.active == 0 {
496                    return;
497                }
498                if let Some(text) = cx.style.text.get_mut(cx.current) {
499                    let old_display = text.clone();
500                    let del_offset = offset_for_delete_backwards(&self.selection, text);
501                    let del_range = del_offset..self.selection.active;
502                    let real_del_range = Self::selection_display_to_real(
503                        &old_display,
504                        &self.real_text,
505                        Selection::new(del_range.start, del_range.end),
506                    )
507                    .range();
508
509                    self.selection = Selection::caret(del_range.start);
510
511                    text.edit(del_range, "");
512                    self.real_text.edit(real_del_range, "");
513
514                    self.sync_display_text(cx);
515                    cx.style.needs_access_update(cx.current);
516                }
517            } else if let Some(text) = cx.style.text.get_mut(cx.current) {
518                if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
519                    let old_display = text.clone();
520                    let to_delete = apply_movement(movement, self.selection, text, paragraph, true);
521                    let real_to_delete =
522                        Self::selection_display_to_real(&old_display, &self.real_text, to_delete);
523                    self.selection = to_delete;
524                    let new_cursor_pos = self.selection.min();
525
526                    text.edit(to_delete.range(), "");
527                    self.real_text.edit(real_to_delete.range(), "");
528                    self.selection = Selection::caret(new_cursor_pos);
529
530                    self.sync_display_text(cx);
531                    cx.style.needs_access_update(cx.current);
532                }
533            }
534        } else if let Some(text) = cx.style.text.get_mut(cx.current) {
535            let old_display = text.clone();
536            let del_range = self.selection.range();
537            let real_del_range =
538                Self::selection_display_to_real(&old_display, &self.real_text, self.selection)
539                    .range();
540
541            self.selection = Selection::caret(del_range.start);
542
543            text.edit(del_range, "");
544            self.real_text.edit(real_del_range, "");
545
546            self.sync_display_text(cx);
547            cx.style.needs_access_update(cx.current);
548        }
549
550        self.show_placeholder.set(self.real_text.is_empty());
551    }
552
553    fn reset_text(&mut self, cx: &mut EventContext) {
554        if let Some(text) = cx.style.text.get_mut(cx.current) {
555            text.clear();
556            self.real_text.clear();
557            self.selection = Selection::caret(0);
558            self.show_placeholder.set(true);
559            self.sync_display_text(cx);
560            cx.style.needs_access_update(cx.current);
561        }
562    }
563
564    /// When IME is enabled, the cursor movement logic will be controlled by [`update_preedit`].
565    ///
566    /// [`update_preedit`]: Textbox::update_preedit
567    fn move_cursor(&mut self, cx: &mut EventContext, movement: Movement, selection: bool) {
568        if let Some(text) = cx.style.text.get_mut(cx.current) {
569            if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
570                let new_selection =
571                    apply_movement(movement, self.selection, text, paragraph, selection);
572                self.selection = new_selection;
573                cx.needs_redraw();
574                cx.style.needs_access_update(cx.current);
575            }
576        }
577    }
578
579    fn select_all(&mut self, cx: &mut EventContext) {
580        if self.show_placeholder.get() {
581            return;
582        }
583        if let Some(text) = cx.style.text.get(cx.current) {
584            self.selection.anchor = 0;
585            self.selection.active = text.len();
586            cx.needs_redraw();
587            cx.style.needs_access_update(cx.current);
588        }
589    }
590
591    fn select_word(&mut self, cx: &mut EventContext) {
592        if self.show_placeholder.get() {
593            return;
594        }
595        self.move_cursor(cx, Movement::Word(Direction::Upstream), false);
596        self.move_cursor(cx, Movement::Word(Direction::Downstream), true);
597    }
598
599    fn select_paragraph(&mut self, cx: &mut EventContext) {
600        if self.show_placeholder.get() {
601            return;
602        }
603        self.move_cursor(cx, Movement::ParagraphStart, false);
604        self.move_cursor(cx, Movement::ParagraphEnd, true);
605    }
606
607    fn deselect(&mut self) {
608        self.selection = Selection::caret(self.selection.active);
609    }
610
611    /// These input coordinates should be physical coordinates, i.e. what the mouse events provide.
612    /// The output text coordinates will also be physical, but relative to the top of the text
613    /// glyphs, appropriate for passage to cosmic.
614    fn coordinates_global_to_text(&self, cx: &EventContext, x: f32, y: f32) -> (f32, f32) {
615        let bounds = cx.bounds();
616
617        if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
618            let padding_left = cx
619                .style
620                .padding_left
621                .get_resolved(cx.current, &cx.style.custom_units_props)
622                .unwrap_or_default();
623            let padding_top = cx
624                .style
625                .padding_top
626                .get_resolved(cx.current, &cx.style.custom_units_props)
627                .unwrap_or_default();
628            let padding_right = cx
629                .style
630                .padding_right
631                .get_resolved(cx.current, &cx.style.custom_units_props)
632                .unwrap_or_default();
633            let padding_bottom = cx
634                .style
635                .padding_bottom
636                .get_resolved(cx.current, &cx.style.custom_units_props)
637                .unwrap_or_default();
638
639            let logical_parent_width = cx.physical_to_logical(bounds.w);
640            let logical_parent_height = cx.physical_to_logical(bounds.h);
641
642            let mut padding_left =
643                padding_left.to_px(logical_parent_width, 0.0) * cx.scale_factor();
644            let mut padding_right =
645                padding_right.to_px(logical_parent_width, 0.0) * cx.scale_factor();
646            let padding_top = padding_top.to_px(logical_parent_height, 0.0) * cx.scale_factor();
647            let padding_bottom =
648                padding_bottom.to_px(logical_parent_height, 0.0) * cx.scale_factor();
649
650            if resolved_text_direction(cx.style, cx.current) == crate::style::Direction::RightToLeft
651            {
652                std::mem::swap(&mut padding_left, &mut padding_right);
653            }
654
655            let (mut top, _) = match cx.style.alignment.get(cx.current).copied().unwrap_or_default()
656            {
657                Alignment::TopLeft => (0.0, 0.0),
658                Alignment::TopCenter => (0.0, 0.5),
659                Alignment::TopRight => (0.0, 1.0),
660                Alignment::Left => (0.5, 0.0),
661                Alignment::Center => (0.5, 0.5),
662                Alignment::Right => (0.5, 1.0),
663                Alignment::BottomLeft => (1.0, 0.0),
664                Alignment::BottomCenter => (1.0, 0.5),
665                Alignment::BottomRight => (1.0, 1.0),
666            };
667
668            top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
669
670            let x = x - bounds.x - padding_left;
671            let y = y - bounds.y - padding_top - top;
672
673            (x, y)
674        } else {
675            (x, y)
676        }
677    }
678
679    /// This function takes window-global physical coordinates.
680    fn hit(&mut self, cx: &mut EventContext, x: f32, y: f32, selection: bool) {
681        if let Some(text) = cx.style.text.get(cx.current) {
682            if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
683                let (mut x, mut y) = (x, y);
684
685                if let Some(inv_transform) = cx.transform().invert() {
686                    let point = inv_transform.map_point(Point::new(x, y));
687                    x = point.x;
688                    y = point.y;
689                }
690
691                x -= self.transform.borrow().0;
692                y -= self.transform.borrow().1;
693
694                let gp = paragraph
695                    .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
696                let num_graphemes = text.graphemes(true).count();
697                let pos = (gp.position as usize).min(num_graphemes);
698                let mut cursor = text.len();
699                for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
700                    if pos == i {
701                        cursor = j;
702                        break;
703                    }
704                }
705
706                if selection {
707                    self.selection.active = cursor;
708                } else {
709                    self.selection = Selection::caret(cursor);
710                }
711
712                cx.needs_redraw();
713                cx.style.needs_access_update(cx.current);
714            }
715        }
716    }
717
718    /// This function takes window-global physical coordinates.
719    fn drag(&mut self, cx: &mut EventContext, x: f32, y: f32) {
720        if let Some(text) = cx.style.text.get(cx.current) {
721            if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
722                let (mut x, mut y) = (x, y);
723
724                if let Some(inv_transform) = cx.transform().invert() {
725                    let point = inv_transform.map_point(Point::new(x, y));
726                    x = point.x;
727                    y = point.y;
728                }
729
730                x -= self.transform.borrow().0;
731                y -= self.transform.borrow().1;
732
733                let gp = paragraph
734                    .get_glyph_position_at_coordinate(self.coordinates_global_to_text(cx, x, y));
735                let num_graphemes = text.graphemes(true).count();
736                let pos = (gp.position as usize).min(num_graphemes);
737
738                let mut cursor = text.len();
739                for (i, (j, _)) in text.grapheme_indices(true).enumerate() {
740                    if pos == i {
741                        cursor = j;
742                        break;
743                    }
744                }
745
746                self.selection.active = cursor;
747
748                cx.needs_redraw();
749                cx.style.needs_access_update(cx.current);
750            }
751        }
752    }
753
754    // /// This function takes window-global physical dimensions.
755    // fn scroll(&mut self, cx: &mut EventContext, x: f32, y: f32) {}
756
757    #[cfg(feature = "clipboard")]
758    fn clone_selected(&self, cx: &mut EventContext) -> Option<String> {
759        if let Some(text) = cx.style.text.get(cx.current) {
760            let real_selection =
761                Self::selection_display_to_real(text, &self.real_text, self.selection);
762            let substring = &self.real_text[real_selection.range()];
763            return Some(substring.to_string());
764        }
765
766        None
767    }
768
769    fn clone_text(&self, _cx: &mut EventContext) -> String {
770        if self.show_placeholder.get() {
771            return String::new();
772        }
773
774        self.real_text.clone()
775    }
776
777    fn clamp_insert_text(&self, txt: &str, replace_range: Range<usize>) -> String {
778        let Some(max_length) = self.max_length.get() else {
779            return txt.to_string();
780        };
781
782        let current_len = self.real_text.graphemes(true).count();
783        let replaced_len = self.real_text[replace_range].graphemes(true).count();
784        let preserved_len = current_len.saturating_sub(replaced_len);
785        let remaining = max_length.saturating_sub(preserved_len);
786
787        if remaining == 0 {
788            return String::new();
789        }
790
791        txt.graphemes(true).take(remaining).collect()
792    }
793
794    fn is_text_valid(&self, text: &str) -> bool {
795        if let Ok(value) = text.parse::<T>() {
796            if let Some(validate) = &self.validate { validate(&value) } else { true }
797        } else {
798            false
799        }
800    }
801
802    fn reset_caret_timer(&mut self, cx: &mut EventContext) {
803        cx.stop_timer(self.caret_timer);
804        if !cx.is_read_only() {
805            self.show_caret.set(true);
806            cx.start_timer(self.caret_timer);
807        }
808    }
809
810    fn reset_ime_position(&mut self, cx: &mut EventContext) {
811        // TODO: Make the position of IME follow the cursor.
812        cx.event_queue.push_back(
813            Event::new(WindowEvent::SetImeCursorArea(
814                (cx.bounds().x as u32, cx.bounds().y as u32),
815                ((cx.bounds().width()) as u32, cx.bounds().height() as u32),
816            ))
817            .target(cx.current),
818        );
819    }
820
821    fn draw_selection(&self, cx: &mut DrawContext, canvas: &Canvas) {
822        if !self.selection.is_caret() {
823            if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
824                if let Some(text) = cx.style.text.get(cx.current) {
825                    let min = text.current_grapheme_offset(self.selection.min());
826                    let max = text.current_grapheme_offset(self.selection.max());
827
828                    let cursor_rects = paragraph.get_rects_for_range(
829                        min..max,
830                        RectHeightStyle::Tight,
831                        RectWidthStyle::Tight,
832                    );
833
834                    for cursor_rect in cursor_rects {
835                        let bounds = cx.bounds();
836
837                        let alignment = cx.alignment();
838
839                        let (mut top, left) = match alignment {
840                            Alignment::TopLeft => (0.0, 0.0),
841                            Alignment::TopCenter => (0.0, 0.5),
842                            Alignment::TopRight => (0.0, 1.0),
843                            Alignment::Left => (0.5, 0.0),
844                            Alignment::Center => (0.5, 0.5),
845                            Alignment::Right => (0.5, 1.0),
846                            Alignment::BottomLeft => (1.0, 0.0),
847                            Alignment::BottomCenter => (1.0, 0.5),
848                            Alignment::BottomRight => (1.0, 1.0),
849                        };
850
851                        let padding_top = match cx.padding_top() {
852                            Units::Pixels(val) => val,
853                            _ => 0.0,
854                        };
855
856                        let padding_bottom = match cx.padding_bottom() {
857                            Units::Pixels(val) => val,
858                            _ => 0.0,
859                        };
860
861                        top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
862
863                        let mut padding_left = match cx.padding_left() {
864                            Units::Pixels(val) => val,
865                            _ => 0.0,
866                        };
867
868                        let mut padding_right = match cx.padding_right() {
869                            Units::Pixels(val) => val,
870                            _ => 0.0,
871                        };
872
873                        if resolved_text_direction(cx.style, cx.current)
874                            == crate::style::Direction::RightToLeft
875                        {
876                            std::mem::swap(&mut padding_left, &mut padding_right);
877                        }
878
879                        let x = bounds.x + padding_left + cursor_rect.rect.left + left;
880                        let y = bounds.y + padding_top + cursor_rect.rect.top + top;
881
882                        let x2 = x + (cursor_rect.rect.right - cursor_rect.rect.left);
883                        let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
884
885                        let mut paint = Paint::default();
886                        paint.set_anti_alias(true);
887                        paint.set_style(PaintStyle::Fill);
888                        paint.set_color(cx.selection_color());
889
890                        canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
891                    }
892                }
893            }
894        }
895    }
896
897    /// Draw text caret for the current view.
898    pub fn draw_text_caret(&self, cx: &mut DrawContext, canvas: &Canvas) {
899        if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
900            if let Some(text) = cx.style.text.get(cx.current) {
901                let bounds = cx.bounds();
902
903                let current = text.current_grapheme_offset(self.selection.active);
904
905                let grapheme_count = text.graphemes(true).count();
906                let (range_start, range_end, use_trailing_edge) = if current < grapheme_count {
907                    (current, current + 1, false)
908                } else if current > 0 {
909                    // At end-of-text, use the previous grapheme box and place the caret on its trailing edge.
910                    (current - 1, current, true)
911                } else {
912                    // Empty text: anchor caret to the paragraph's first position.
913                    (0, 1, false)
914                };
915
916                let rects = paragraph.get_rects_for_range(
917                    range_start..range_end,
918                    RectHeightStyle::Tight,
919                    RectWidthStyle::Tight,
920                );
921
922                let Some(cursor_rect) = rects.first() else {
923                    return;
924                };
925
926                let alignment = cx.alignment();
927
928                let (mut top, _) = match alignment {
929                    Alignment::TopLeft => (0.0, 0.0),
930                    Alignment::TopCenter => (0.0, 0.5),
931                    Alignment::TopRight => (0.0, 1.0),
932                    Alignment::Left => (0.5, 0.0),
933                    Alignment::Center => (0.5, 0.5),
934                    Alignment::Right => (0.5, 1.0),
935                    Alignment::BottomLeft => (1.0, 0.0),
936                    Alignment::BottomCenter => (1.0, 0.5),
937                    Alignment::BottomRight => (1.0, 1.0),
938                };
939
940                let padding_top = match cx.padding_top() {
941                    Units::Pixels(val) => val,
942                    _ => 0.0,
943                };
944
945                let padding_bottom = match cx.padding_bottom() {
946                    Units::Pixels(val) => val,
947                    _ => 0.0,
948                };
949
950                top *= bounds.height() - padding_top - padding_bottom - paragraph.height();
951
952                let mut padding_left = match cx.padding_left() {
953                    Units::Pixels(val) => val,
954                    _ => 0.0,
955                };
956
957                let mut padding_right = match cx.padding_right() {
958                    Units::Pixels(val) => val,
959                    _ => 0.0,
960                };
961
962                if resolved_text_direction(cx.style, cx.current)
963                    == crate::style::Direction::RightToLeft
964                {
965                    std::mem::swap(&mut padding_left, &mut padding_right);
966                }
967
968                let caret_x =
969                    if use_trailing_edge { cursor_rect.rect.right } else { cursor_rect.rect.left };
970
971                let x = (bounds.x + padding_left + caret_x).round();
972                let y = (bounds.y + padding_top + cursor_rect.rect.top + top).round();
973
974                let x2 = x + 1.0;
975                let y2 = y + (cursor_rect.rect.bottom - cursor_rect.rect.top);
976
977                let mut paint = Paint::default();
978                paint.set_anti_alias(true);
979                paint.set_style(PaintStyle::Fill);
980                paint.set_color(cx.caret_color());
981
982                canvas.draw_rect(Rect::new(x, y, x2, y2), &paint);
983
984                let mut transform = self.transform.borrow_mut();
985
986                let text_bounds = BoundingBox::from_min_max(
987                    bounds.x + padding_left,
988                    bounds.y + padding_top + top,
989                    bounds.x + padding_left + paragraph.max_intrinsic_width(),
990                    bounds.y + padding_top + top + paragraph.height(),
991                );
992
993                let mut bounds = bounds;
994
995                bounds =
996                    bounds.shrink_sides(padding_left, padding_top, padding_right, padding_bottom);
997
998                let (tx, ty) =
999                    enforce_text_bounds(&text_bounds, &bounds, (transform.0, transform.1));
1000
1001                let caret_box = BoundingBox::from_min_max(x, y, x2, y2);
1002
1003                let (new_tx, new_ty) = ensure_visible(&caret_box, &bounds, (tx, ty));
1004
1005                if new_tx != transform.0 || new_ty != transform.1 {
1006                    *transform = (new_tx, new_ty);
1007                    cx.needs_redraw();
1008                }
1009            }
1010        }
1011    }
1012}
1013
1014impl<R, T> Handle<'_, Textbox<R, T>>
1015where
1016    R: Res<T> + 'static,
1017    T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1018{
1019    /// Sets the callback triggered when a textbox is edited, i.e. text is inserted/deleted.
1020    ///
1021    /// Callback provides the current text of the textbox.
1022    pub fn on_edit<F>(self, callback: F) -> Self
1023    where
1024        F: 'static + Fn(&mut EventContext, String) + Send + Sync,
1025    {
1026        self.modify(|textbox| textbox.on_edit = Some(Box::new(callback)))
1027    }
1028
1029    /// Sets the callback triggered when a textbox is submitted,
1030    /// i.e. when the enter key is pressed with a single-line textbox or the textbox loses focus.
1031    ///
1032    /// Callback provides the text of the textbox and a flag to indicate if the submit was due to a key press or a loss of focus.
1033    pub fn on_submit<F>(self, callback: F) -> Self
1034    where
1035        F: 'static + Fn(&mut EventContext, T, bool) + Send + Sync,
1036    {
1037        self.modify(|textbox| textbox.on_submit = Some(Box::new(callback)))
1038    }
1039
1040    /// Sets the callback triggered when a textbox is blurred, i.e. the mouse is pressed outside of the textbox.
1041    pub fn on_blur<F>(self, callback: F) -> Self
1042    where
1043        F: 'static + Fn(&mut EventContext) + Send + Sync,
1044    {
1045        self.modify(|textbox| textbox.on_blur = Some(Box::new(callback)))
1046    }
1047
1048    /// Sets the callback triggered when a textbox edit is cancelled, i.e. the escape key is pressed while editing.
1049    pub fn on_cancel<F>(self, callback: F) -> Self
1050    where
1051        F: 'static + Fn(&mut EventContext) + Send + Sync,
1052    {
1053        self.modify(|textbox| textbox.on_cancel = Some(Box::new(callback)))
1054    }
1055
1056    /// Sets a validation closure which is called when the textbox is edited and sets the validity attribute to the output of the closure.
1057    ///
1058    /// If a textbox is modified with the validate modifier then the `on_submit` will not be called if the text is invalid.
1059    pub fn validate<F>(self, is_valid: F) -> Self
1060    where
1061        F: 'static + Fn(&T) -> bool + Send + Sync,
1062    {
1063        self.modify(|textbox| textbox.validate = Some(Box::new(is_valid)))
1064    }
1065
1066    /// Sets the placeholder text that appears when the textbox has no value.
1067    pub fn placeholder<P: ToStringLocalized + Clone + 'static>(
1068        self,
1069        text: impl Res<P> + 'static,
1070    ) -> Self {
1071        let text = text.to_signal(self.cx);
1072        self.bind(text, move |mut handle| {
1073            let text = text.get();
1074            let txt = text.to_string_local(&handle);
1075            let entity = handle.entity();
1076            handle = handle.modify(|textbox| textbox.placeholder.set(txt));
1077            handle.context().style.needs_access_update(entity);
1078        })
1079    }
1080
1081    /// Sets an optional character used to visually mask textbox text.
1082    ///
1083    /// Use `Some('*')` (or any character) to enable masking and `None` to disable it.
1084    pub fn mask_char<U: Into<Option<char>> + Clone + 'static>(
1085        self,
1086        mask: impl Res<U> + 'static,
1087    ) -> Self {
1088        let mask = mask.to_signal(self.cx);
1089        self.bind(mask, move |mut handle| {
1090            let entity = handle.entity();
1091            let new_mask = mask.get().into();
1092            let mut display_text = String::new();
1093            handle = handle.modify(|textbox| {
1094                let old_display = textbox.display_text_from_real();
1095                textbox.mask_char.set_if_changed(new_mask);
1096                let new_display = textbox.display_text_from_real();
1097                textbox.remap_selection_for_display_change(&old_display, &new_display);
1098                display_text = new_display;
1099            });
1100            handle = handle.text(display_text);
1101            handle.context().style.needs_text_update(entity);
1102            handle.context().needs_redraw(entity);
1103        })
1104    }
1105
1106    /// Sets whether masked text should be visible.
1107    pub fn mask_visible<U: Into<bool> + Clone + 'static>(
1108        self,
1109        visible: impl Res<U> + 'static,
1110    ) -> Self {
1111        let visible = visible.to_signal(self.cx);
1112        self.bind(visible, move |mut handle| {
1113            let entity = handle.entity();
1114            let new_visible = visible.get().into();
1115            let mut display_text = String::new();
1116            let mut changed = false;
1117
1118            handle = handle.modify(|textbox| {
1119                if textbox.mask_visible != new_visible {
1120                    let old_display = textbox.display_text_from_real();
1121                    textbox.mask_visible = new_visible;
1122                    let new_display = textbox.display_text_from_real();
1123                    textbox.remap_selection_for_display_change(&old_display, &new_display);
1124                    display_text = new_display;
1125                    changed = true;
1126                }
1127            });
1128
1129            if changed {
1130                handle = handle.text(display_text);
1131                handle.context().style.needs_text_update(entity);
1132                handle.context().needs_redraw(entity);
1133            }
1134        })
1135    }
1136
1137    /// Sets an optional maximum number of graphemes for textbox input.
1138    ///
1139    /// Use `Some(n)` to limit input length and `None` to remove the limit.
1140    pub fn max_length<U: Into<Option<usize>> + Clone + 'static>(
1141        self,
1142        max_length: impl Res<U> + 'static,
1143    ) -> Self {
1144        let max_length = max_length.to_signal(self.cx);
1145        self.bind(max_length, move |handle| {
1146            let value = max_length.get().into();
1147            handle.modify(|textbox| textbox.max_length.set_if_changed(value));
1148        })
1149    }
1150
1151    /// Sets whether text in this textbox can be copied to the clipboard.
1152    pub fn can_copy<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1153        let state = state.to_signal(self.cx);
1154        self.bind(state, move |handle| {
1155            let value = state.get().into();
1156            handle.modify(|textbox| textbox.can_copy.set_if_changed(value));
1157        })
1158    }
1159
1160    /// Sets whether text can be pasted into this textbox from the clipboard.
1161    pub fn can_paste<U: Into<bool> + Clone + 'static>(self, state: impl Res<U> + 'static) -> Self {
1162        let state = state.to_signal(self.cx);
1163        self.bind(state, move |handle| {
1164            let value = state.get().into();
1165            handle.modify(|textbox| textbox.can_paste.set_if_changed(value));
1166        })
1167    }
1168}
1169
1170/// Converts a byte offset (relative to line start) into a character index
1171/// within the `character_lengths` array for AccessKit text positioning.
1172fn byte_offset_to_char_index(character_lengths: &[u8], byte_offset: usize) -> usize {
1173    let mut cumulative = 0;
1174    for (i, &len) in character_lengths.iter().enumerate() {
1175        cumulative += len as usize;
1176        if byte_offset < cumulative {
1177            return i;
1178        }
1179    }
1180    character_lengths.len()
1181}
1182
1183impl<R, T> View for Textbox<R, T>
1184where
1185    R: Res<T> + 'static,
1186    T: Clone + ToStringLocalized + std::str::FromStr + 'static,
1187{
1188    fn element(&self) -> Option<&'static str> {
1189        Some("textbox")
1190    }
1191
1192    fn accessibility(&self, cx: &mut AccessContext, node: &mut AccessNode) {
1193        if !self.placeholder.get().is_empty() {
1194            node.set_placeholder(self.placeholder.get().clone());
1195        }
1196
1197        let node_id = node.node_id();
1198
1199        let selection = self.selection;
1200
1201        let mut selection_active_line = None;
1202        let mut selection_anchor_line = None;
1203        let mut selection_active_cursor = 0;
1204        let mut selection_anchor_cursor = 0;
1205        let mut first_line_node_id = None;
1206
1207        let text = if self.show_placeholder.get() {
1208            ""
1209        } else {
1210            cx.style.text.get(cx.current).map(|t| t.as_str()).unwrap_or("")
1211        };
1212        // build_paragraph() appends a zero-width space (\u{200B}, 3 UTF-8 bytes)
1213        // to every paragraph, so skia's line metrics include indices beyond the
1214        // actual text. We use text.len() as the upper bound for all slicing.
1215        let text_len = text.len();
1216
1217        if let Some(paragraph) = cx.text_context.text_paragraphs.get(cx.current) {
1218            let text_direction = if resolved_text_direction(cx.style, cx.current)
1219                == crate::style::Direction::RightToLeft
1220            {
1221                TextDirection::RightToLeft
1222            } else {
1223                TextDirection::LeftToRight
1224            };
1225
1226            let line_metrics = paragraph.get_line_metrics();
1227            for line in line_metrics.iter() {
1228                // Skip lines that start beyond the actual text (i.e., the ZWS-only line)
1229                if line.start_index >= text_len && text_len > 0 {
1230                    continue;
1231                }
1232
1233                // We need a child node per line
1234                let mut line_node = AccessNode::new_from_parent(node_id, line.line_number);
1235                line_node.set_role(Role::TextRun);
1236                line_node.set_text_direction(text_direction);
1237                line_node.set_bounds(BoundingBox {
1238                    x: line.left as f32,
1239                    y: (line.baseline - line.ascent) as f32,
1240                    w: line.width as f32,
1241                    h: line.height as f32,
1242                });
1243
1244                // Only iterate over glyphs within the actual text range
1245                let glyph_end = line.end_index.min(text_len);
1246                if line.start_index > glyph_end {
1247                    continue;
1248                }
1249                let estimated_chars = glyph_end.saturating_sub(line.start_index);
1250                let mut character_lengths: Vec<u8> = Vec::with_capacity(estimated_chars);
1251                let mut character_positions: Vec<f32> = Vec::with_capacity(estimated_chars);
1252                let mut character_widths: Vec<f32> = Vec::with_capacity(estimated_chars);
1253                let mut glyph_pos = line.start_index;
1254
1255                while glyph_pos < glyph_end {
1256                    if let Some(cluster_info) = paragraph.get_glyph_cluster_at(glyph_pos) {
1257                        let length = cluster_info.text_range.end - cluster_info.text_range.start;
1258                        if length == 0 {
1259                            break;
1260                        }
1261
1262                        character_lengths.push(length as u8);
1263                        character_positions.push(cluster_info.bounds.left());
1264                        character_widths.push(cluster_info.bounds.width());
1265
1266                        glyph_pos += length;
1267                    } else {
1268                        break;
1269                    }
1270                }
1271
1272                // Include the newline character for hard breaks, as AccessKit needs it
1273                let mut line_end = if line.hard_break {
1274                    line.end_including_newline.min(text_len)
1275                } else {
1276                    glyph_end
1277                };
1278                if line_end < line.start_index {
1279                    line_end = line.start_index;
1280                }
1281                let line_text = text.get(line.start_index..line_end).unwrap_or("").to_owned();
1282
1283                if line.hard_break && line.end_including_newline <= text_len {
1284                    character_lengths.push(1);
1285                    character_positions.push(line.width as f32);
1286                    character_widths.push(0.0);
1287                }
1288
1289                let mut word_starts = Vec::new();
1290                let mut previous_is_alphanumeric = text
1291                    .get(..line.start_index)
1292                    .and_then(|prefix| prefix.graphemes(true).next_back())
1293                    .and_then(|grapheme| grapheme.chars().next())
1294                    .is_some_and(|ch| ch.is_alphanumeric());
1295
1296                for (character_index, grapheme) in line_text.graphemes(true).enumerate() {
1297                    let current_is_alphanumeric =
1298                        grapheme.chars().next().is_some_and(|ch| ch.is_alphanumeric());
1299
1300                    if current_is_alphanumeric
1301                        && !previous_is_alphanumeric
1302                        && let Ok(character_index) = u8::try_from(character_index)
1303                    {
1304                        word_starts.push(character_index);
1305                    }
1306
1307                    previous_is_alphanumeric = current_is_alphanumeric;
1308                }
1309
1310                if first_line_node_id.is_none() {
1311                    first_line_node_id = Some(line_node.node_id());
1312                }
1313
1314                // Check if this line contains the selection active (focus) position
1315                if selection.active >= line.start_index && selection.active <= line_end {
1316                    selection_active_line = Some(line_node.node_id());
1317                    selection_active_cursor = byte_offset_to_char_index(
1318                        &character_lengths,
1319                        selection.active - line.start_index,
1320                    );
1321                }
1322
1323                // Check if this line contains the selection anchor position
1324                if selection.anchor >= line.start_index && selection.anchor <= line_end {
1325                    selection_anchor_line = Some(line_node.node_id());
1326                    selection_anchor_cursor = byte_offset_to_char_index(
1327                        &character_lengths,
1328                        selection.anchor - line.start_index,
1329                    );
1330                }
1331
1332                line_node.set_value(line_text.into_boxed_str());
1333                line_node.set_character_lengths(character_lengths.into_boxed_slice());
1334                line_node.set_character_positions(character_positions.into_boxed_slice());
1335                line_node.set_character_widths(character_widths.into_boxed_slice());
1336                line_node.set_word_starts(word_starts.into_boxed_slice());
1337
1338                node.add_child(line_node);
1339            }
1340        }
1341
1342        if let Some(fallback) = first_line_node_id {
1343            node.set_text_selection(TextSelection {
1344                anchor: TextPosition {
1345                    node: selection_anchor_line.unwrap_or(fallback),
1346                    character_index: selection_anchor_cursor,
1347                },
1348                focus: TextPosition {
1349                    node: selection_active_line.unwrap_or(fallback),
1350                    character_index: selection_active_cursor,
1351                },
1352            });
1353        }
1354    }
1355
1356    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
1357        // Window Events
1358        event.map(|window_event, meta| match window_event {
1359            WindowEvent::MouseDown(MouseButton::Left) => {
1360                if meta.origin == cx.current {
1361                    return;
1362                }
1363
1364                if cx.is_over() {
1365                    if !cx.is_disabled() {
1366                        cx.focus_with_visibility(true);
1367                        cx.capture();
1368                        cx.lock_cursor_icon();
1369
1370                        if !self.edit {
1371                            cx.emit(TextEvent::StartEdit);
1372                        }
1373                        self.reset_caret_timer(cx);
1374                        cx.emit(TextEvent::Hit(
1375                            cx.mouse.cursor_x,
1376                            cx.mouse.cursor_y,
1377                            cx.modifiers.shift(),
1378                        ));
1379                    }
1380                } else {
1381                    cx.release();
1382
1383                    // Forward event to hovered
1384                    cx.event_queue.push_back(
1385                        Event::new(WindowEvent::MouseDown(MouseButton::Left)).target(cx.hovered()),
1386                    );
1387                    cx.event_queue.push_back(
1388                        Event::new(WindowEvent::PressDown { mouse: true }).target(cx.hovered()),
1389                    );
1390                }
1391            }
1392
1393            WindowEvent::FocusIn => {
1394                if cx.mouse.left.pressed != cx.current()
1395                    || cx.mouse.left.state == MouseButtonState::Released
1396                {
1397                    cx.emit(TextEvent::StartEdit);
1398                }
1399            }
1400
1401            WindowEvent::FocusOut => {
1402                cx.emit(TextEvent::EndEdit);
1403            }
1404
1405            WindowEvent::MouseDoubleClick(MouseButton::Left) => {
1406                cx.emit(TextEvent::SelectWord);
1407            }
1408
1409            WindowEvent::MouseTripleClick(MouseButton::Left) => {
1410                cx.emit(TextEvent::SelectParagraph);
1411            }
1412
1413            WindowEvent::MouseUp(MouseButton::Left) => {
1414                self.reset_caret_timer(cx);
1415                cx.unlock_cursor_icon();
1416                cx.release();
1417            }
1418
1419            WindowEvent::MouseMove(x, y) => {
1420                if cx.mouse.left.state == MouseButtonState::Pressed
1421                    && cx.mouse.left.pressed == cx.current
1422                {
1423                    if self.edit {
1424                        self.reset_caret_timer(cx);
1425                    }
1426                    if cx.mouse.left.pos_down.0 != *x || cx.mouse.left.pos_down.1 != *y {
1427                        cx.emit(TextEvent::Drag(*x, *y));
1428                    }
1429                }
1430            }
1431
1432            WindowEvent::MouseScroll(x, y) => {
1433                cx.emit(TextEvent::Scroll(*x, *y));
1434            }
1435
1436            WindowEvent::CharInput(c) => {
1437                if *c != '\u{1b}' && // Escape
1438                    *c != '\u{8}' && // Backspace
1439                    *c != '\u{9}' && // Tab
1440                    *c != '\u{7f}' && // Delete
1441                    *c != '\u{0d}' && // Carriage return
1442                    !cx.modifiers.ctrl() &&
1443                    !cx.modifiers.logo() &&
1444                    self.edit &&
1445                    !cx.is_read_only()
1446                {
1447                    self.reset_caret_timer(cx);
1448                    cx.emit(TextEvent::InsertText(String::from(*c)));
1449                }
1450            }
1451
1452            WindowEvent::ImeCommit(text) => {
1453                if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1454                    self.reset_caret_timer(cx);
1455                    cx.emit(TextEvent::ClearPreedit);
1456                    cx.emit(TextEvent::InsertText(text.to_string()));
1457
1458                    self.reset_ime_position(cx);
1459                }
1460            }
1461
1462            WindowEvent::ImePreedit(text, cursor) => {
1463                if !cx.modifiers.ctrl() && !cx.modifiers.logo() && self.edit && !cx.is_read_only() {
1464                    self.reset_caret_timer(cx);
1465                    cx.emit(TextEvent::UpdatePreedit(text.to_string(), *cursor));
1466                }
1467            }
1468
1469            WindowEvent::KeyDown(code, _) => match code {
1470                Code::Enter => {
1471                    if matches!(self.kind, TextboxKind::SingleLine) {
1472                        cx.emit(TextEvent::Submit(true));
1473                    } else if !cx.is_read_only() {
1474                        self.reset_caret_timer(cx);
1475                        cx.emit(TextEvent::InsertText("\n".to_owned()));
1476                    }
1477                }
1478
1479                // Note: no `Code::Space` arm — the space character arrives
1480                // through `WindowEvent::CharInput(' ')` above, which already
1481                // inserts it (and correctly suppresses insertion when Ctrl
1482                // or Cmd is held). Handling it here as well produced double
1483                // insertion on platforms that emit both events for a plain
1484                // spacebar press.
1485                Code::ArrowLeft => {
1486                    self.reset_caret_timer(cx);
1487                    // macOS convention: Option (alt) for word movement,
1488                    // Cmd (logo) for line-boundary movement.
1489                    // Other platforms: Ctrl for word movement.
1490                    #[cfg(target_os = "macos")]
1491                    let movement = if cx.modifiers.logo() {
1492                        Movement::LineStart
1493                    } else if cx.modifiers.alt() {
1494                        Movement::Word(Direction::Left)
1495                    } else {
1496                        Movement::Grapheme(Direction::Left)
1497                    };
1498                    #[cfg(not(target_os = "macos"))]
1499                    let movement = if cx.modifiers.ctrl() {
1500                        Movement::Word(Direction::Left)
1501                    } else {
1502                        Movement::Grapheme(Direction::Left)
1503                    };
1504
1505                    cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1506                }
1507
1508                Code::ArrowRight => {
1509                    self.reset_caret_timer(cx);
1510
1511                    #[cfg(target_os = "macos")]
1512                    let movement = if cx.modifiers.logo() {
1513                        Movement::LineEnd
1514                    } else if cx.modifiers.alt() {
1515                        Movement::Word(Direction::Right)
1516                    } else {
1517                        Movement::Grapheme(Direction::Right)
1518                    };
1519                    #[cfg(not(target_os = "macos"))]
1520                    let movement = if cx.modifiers.ctrl() {
1521                        Movement::Word(Direction::Right)
1522                    } else {
1523                        Movement::Grapheme(Direction::Right)
1524                    };
1525
1526                    cx.emit(TextEvent::MoveCursor(movement, cx.modifiers.shift()));
1527                }
1528
1529                Code::ArrowUp => {
1530                    self.reset_caret_timer(cx);
1531                    if self.kind != TextboxKind::SingleLine {
1532                        cx.emit(TextEvent::MoveCursor(
1533                            Movement::Vertical(VerticalMovement::LineUp),
1534                            cx.modifiers.shift(),
1535                        ));
1536                    }
1537                }
1538
1539                Code::ArrowDown => {
1540                    self.reset_caret_timer(cx);
1541                    if self.kind != TextboxKind::SingleLine {
1542                        cx.emit(TextEvent::MoveCursor(
1543                            Movement::Vertical(VerticalMovement::LineDown),
1544                            cx.modifiers.shift(),
1545                        ));
1546                    }
1547                }
1548
1549                Code::Backspace => {
1550                    self.reset_caret_timer(cx);
1551                    if !cx.is_read_only() {
1552                        #[cfg(target_os = "macos")]
1553                        let movement = if cx.modifiers.logo() {
1554                            // Cmd+Backspace deletes from caret to the visual
1555                            // line start on macOS, matching Cmd+Left cursor
1556                            // movement (which uses `Movement::LineStart`).
1557                            Movement::LineStart
1558                        } else if cx.modifiers.alt() {
1559                            Movement::Word(Direction::Upstream)
1560                        } else {
1561                            Movement::Grapheme(Direction::Upstream)
1562                        };
1563                        #[cfg(not(target_os = "macos"))]
1564                        let movement = if cx.modifiers.ctrl() {
1565                            Movement::Word(Direction::Upstream)
1566                        } else {
1567                            Movement::Grapheme(Direction::Upstream)
1568                        };
1569
1570                        cx.emit(TextEvent::DeleteText(movement));
1571                    }
1572                }
1573
1574                Code::Delete => {
1575                    self.reset_caret_timer(cx);
1576                    if !cx.is_read_only() {
1577                        #[cfg(target_os = "macos")]
1578                        let movement = if cx.modifiers.alt() {
1579                            Movement::Word(Direction::Downstream)
1580                        } else {
1581                            Movement::Grapheme(Direction::Downstream)
1582                        };
1583                        #[cfg(not(target_os = "macos"))]
1584                        let movement = if cx.modifiers.ctrl() {
1585                            Movement::Word(Direction::Downstream)
1586                        } else {
1587                            Movement::Grapheme(Direction::Downstream)
1588                        };
1589
1590                        cx.emit(TextEvent::DeleteText(movement));
1591                    }
1592                }
1593
1594                Code::Escape => {
1595                    if let Some(callback) = &self.on_cancel {
1596                        (callback)(cx);
1597                    } else {
1598                        cx.emit(TextEvent::EndEdit);
1599                    }
1600                }
1601
1602                Code::Home => {
1603                    self.reset_caret_timer(cx);
1604                    cx.emit(TextEvent::MoveCursor(Movement::LineStart, cx.modifiers.shift()));
1605                }
1606
1607                Code::End => {
1608                    self.reset_caret_timer(cx);
1609                    cx.emit(TextEvent::MoveCursor(Movement::LineEnd, cx.modifiers.shift()));
1610                }
1611
1612                Code::PageUp | Code::PageDown => {
1613                    self.reset_caret_timer(cx);
1614                    let direction = if *code == Code::PageUp {
1615                        Direction::Upstream
1616                    } else {
1617                        Direction::Downstream
1618                    };
1619                    cx.emit(TextEvent::MoveCursor(
1620                        if cx.modifiers.ctrl() {
1621                            Movement::Body(direction)
1622                        } else {
1623                            Movement::Page(direction)
1624                        },
1625                        cx.modifiers.shift(),
1626                    ));
1627                }
1628
1629                Code::KeyA => {
1630                    #[cfg(target_os = "macos")]
1631                    let modifier = Modifiers::SUPER;
1632                    #[cfg(not(target_os = "macos"))]
1633                    let modifier = Modifiers::CTRL;
1634
1635                    if cx.modifiers == &modifier {
1636                        cx.emit(TextEvent::SelectAll);
1637                    }
1638                }
1639
1640                Code::KeyC => {
1641                    #[cfg(target_os = "macos")]
1642                    let modifier = Modifiers::SUPER;
1643                    #[cfg(not(target_os = "macos"))]
1644                    let modifier = Modifiers::CTRL;
1645
1646                    if cx.modifiers == &modifier && self.can_copy.get() {
1647                        cx.emit(TextEvent::Copy);
1648                    }
1649                }
1650
1651                Code::KeyV => {
1652                    #[cfg(target_os = "macos")]
1653                    let modifier = Modifiers::SUPER;
1654                    #[cfg(not(target_os = "macos"))]
1655                    let modifier = Modifiers::CTRL;
1656
1657                    if cx.modifiers == &modifier && self.can_paste.get() {
1658                        cx.emit(TextEvent::Paste);
1659                    }
1660                }
1661
1662                Code::KeyX => {
1663                    #[cfg(target_os = "macos")]
1664                    let modifier = Modifiers::SUPER;
1665                    #[cfg(not(target_os = "macos"))]
1666                    let modifier = Modifiers::CTRL;
1667
1668                    if cx.modifiers == &modifier && !cx.is_read_only() {
1669                        cx.emit(TextEvent::Cut);
1670                    }
1671                }
1672
1673                _ => {}
1674            },
1675
1676            WindowEvent::ActionRequest(ActionRequest {
1677                action: accesskit::Action::SetTextSelection,
1678                target_tree: _,
1679                target_node: _,
1680                data: Some(ActionData::SetTextSelection(_selection)),
1681            }) => {
1682                // TODO: Implement SetTextSelection action for screen reader support.
1683            }
1684
1685            _ => {}
1686        });
1687
1688        // Textbox Events
1689        event.map(|text_event, _| match text_event {
1690            TextEvent::InsertText(text) => {
1691                if self.preedit_backup.is_some() {
1692                    return;
1693                }
1694
1695                self.edited_since_focus = true;
1696                self.edited_once = true;
1697
1698                if self.show_placeholder.get() {
1699                    self.reset_text(cx);
1700                }
1701
1702                self.insert_text(cx, text);
1703
1704                let text = self.clone_text(cx);
1705
1706                cx.set_valid(self.is_text_valid(&text));
1707
1708                if self.edit {
1709                    if let Some(callback) = &self.on_edit {
1710                        (callback)(cx, text);
1711                    }
1712                }
1713            }
1714
1715            TextEvent::UpdatePreedit(preedit, cursor) => {
1716                self.update_preedit(cx, preedit, *cursor);
1717            }
1718
1719            TextEvent::ClearPreedit => {
1720                self.clear_preedit(cx);
1721            }
1722
1723            TextEvent::Clear => {
1724                self.reset_text(cx);
1725                // self.scroll(cx, 0.0, 0.0); // ensure_visible
1726                cx.needs_relayout();
1727                cx.needs_redraw();
1728            }
1729
1730            TextEvent::DeleteText(movement) => {
1731                if self.edit {
1732                    self.edited_since_focus = true;
1733                    self.edited_once = true;
1734                    self.delete_text(cx, *movement);
1735
1736                    let text = self.clone_text(cx);
1737
1738                    cx.set_valid(self.is_text_valid(&text));
1739
1740                    if let Some(callback) = &self.on_edit {
1741                        (callback)(cx, text);
1742                    }
1743                }
1744            }
1745
1746            TextEvent::MoveCursor(movement, selection) => {
1747                if self.edit && !self.show_placeholder.get() && self.preedit_backup.is_none() {
1748                    self.move_cursor(cx, *movement, *selection);
1749                }
1750            }
1751
1752            TextEvent::SetPlaceholder(text) => {
1753                self.placeholder.set(text.clone());
1754                cx.style.needs_access_update(cx.current);
1755            }
1756
1757            TextEvent::StartEdit => {
1758                if !cx.is_disabled() && !self.edit {
1759                    self.edit = true;
1760                    self.edited_since_focus = false;
1761                    cx.focus_with_visibility(true);
1762                    cx.capture();
1763                    self.reset_caret_timer(cx);
1764                    self.reset_ime_position(cx);
1765
1766                    self.text_overflow = cx.style.text_overflow.get_inline(cx.current).copied();
1767                    cx.style.text_overflow.remove(cx.current);
1768
1769                    let text = self.value.get_value(cx);
1770                    let text = text.to_string_local(cx);
1771                    self.real_text = text.clone();
1772
1773                    if text.is_empty() {
1774                        self.show_placeholder.set(true);
1775                        self.selection = Selection::caret(0);
1776                        cx.style.needs_access_update(cx.current);
1777                    } else {
1778                        self.show_placeholder.set(false);
1779                        self.select_all(cx);
1780                    }
1781
1782                    // Keep textbox pristine only until first user edit; once edited,
1783                    // preserve validation across blur/focus cycles.
1784                    if self.edited_once || !text.is_empty() {
1785                        cx.set_valid(self.is_text_valid(&text));
1786                    } else {
1787                        cx.set_valid(true);
1788                    }
1789                }
1790
1791                self.sync_display_text(cx);
1792            }
1793
1794            TextEvent::EndEdit => {
1795                self.deselect();
1796                self.edit = false;
1797                cx.release();
1798                cx.stop_timer(self.caret_timer);
1799
1800                let text = self.clone_text(cx);
1801                self.show_placeholder.set(text.is_empty());
1802
1803                if let Some(text_overflow) = self.text_overflow {
1804                    cx.style.text_overflow.insert(cx.current, text_overflow);
1805                } else {
1806                    cx.style.text_overflow.remove(cx.current);
1807                }
1808
1809                self.select_all(cx);
1810
1811                if self.edited_since_focus {
1812                    cx.set_valid(self.is_text_valid(&text));
1813                }
1814
1815                // Reset transform to 0,0
1816                let mut transform = self.transform.borrow_mut();
1817                *transform = (0.0, 0.0);
1818
1819                // Reset cursor position
1820                self.selection = Selection::caret(0);
1821
1822                self.sync_display_text(cx);
1823                cx.style.needs_access_update(cx.current);
1824            }
1825
1826            TextEvent::Blur => {
1827                // Clicking outside a textbox can end editing while retaining keyboard focus
1828                // (for example when clicking non-focusable chrome). Keep focus but remove
1829                // the visible focus indicator for pointer-driven blur.
1830                if cx.focused() == cx.current() {
1831                    cx.focus_with_visibility(false);
1832                }
1833
1834                if let Some(callback) = &self.on_blur {
1835                    (callback)(cx);
1836                } else {
1837                    cx.emit(TextEvent::Submit(false));
1838                    cx.emit(TextEvent::EndEdit);
1839                }
1840            }
1841
1842            TextEvent::SetMaskVisible(visible) => {
1843                if self.mask_visible != *visible {
1844                    let old_display = self.display_text_from_real();
1845                    self.mask_visible = *visible;
1846                    let new_display = self.display_text_from_real();
1847                    self.remap_selection_for_display_change(&old_display, &new_display);
1848                    self.sync_display_text(cx);
1849                    cx.needs_redraw();
1850                }
1851            }
1852
1853            TextEvent::ToggleMaskVisible => {
1854                let old_display = self.display_text_from_real();
1855                self.mask_visible = !self.mask_visible;
1856                let new_display = self.display_text_from_real();
1857                self.remap_selection_for_display_change(&old_display, &new_display);
1858                self.sync_display_text(cx);
1859                cx.needs_redraw();
1860            }
1861
1862            TextEvent::Submit(reason) => {
1863                if let Some(callback) = &self.on_submit {
1864                    let text = self.clone_text(cx);
1865                    let is_valid = self.is_text_valid(&text);
1866                    cx.set_valid(is_valid);
1867                    if is_valid && let Ok(value) = text.parse::<T>() {
1868                        (callback)(cx, value, *reason);
1869                    }
1870                }
1871            }
1872
1873            TextEvent::SelectAll => {
1874                self.select_all(cx);
1875            }
1876
1877            TextEvent::SelectWord => {
1878                self.select_word(cx);
1879            }
1880
1881            TextEvent::SelectParagraph => {
1882                self.select_paragraph(cx);
1883            }
1884
1885            TextEvent::Hit(posx, posy, selection) => {
1886                if !self.show_placeholder.get() {
1887                    self.hit(cx, *posx, *posy, *selection);
1888                }
1889            }
1890
1891            TextEvent::Drag(posx, posy) => {
1892                if !self.show_placeholder.get() {
1893                    self.drag(cx, *posx, *posy);
1894                }
1895            }
1896
1897            TextEvent::Scroll(_x, _y) => {
1898                //self.scroll(cx, *x, *y);
1899            }
1900
1901            TextEvent::Copy =>
1902            {
1903                #[cfg(feature = "clipboard")]
1904                if self.edit && self.can_copy.get() {
1905                    if let Some(selected_text) = self.clone_selected(cx) {
1906                        if !selected_text.is_empty() {
1907                            cx.set_clipboard(selected_text)
1908                                .expect("Failed to add text to clipboard");
1909                        }
1910                    }
1911                }
1912            }
1913
1914            TextEvent::Paste =>
1915            {
1916                #[cfg(feature = "clipboard")]
1917                if self.edit && self.can_paste.get() {
1918                    if let Ok(text) = cx.get_clipboard() {
1919                        cx.emit(TextEvent::InsertText(text));
1920                    }
1921                }
1922            }
1923
1924            TextEvent::Cut =>
1925            {
1926                #[cfg(feature = "clipboard")]
1927                if self.edit {
1928                    if let Some(selected_text) = self.clone_selected(cx) {
1929                        if !selected_text.is_empty() {
1930                            self.edited_since_focus = true;
1931                            self.edited_once = true;
1932                            cx.set_clipboard(selected_text)
1933                                .expect("Failed to add text to clipboard");
1934                            self.delete_text(cx, Movement::Grapheme(Direction::Upstream));
1935
1936                            let text = self.clone_text(cx);
1937
1938                            cx.set_valid(self.is_text_valid(&text));
1939
1940                            if let Some(callback) = &self.on_edit {
1941                                (callback)(cx, text);
1942                            }
1943                        }
1944                    }
1945                }
1946            }
1947
1948            TextEvent::ToggleCaret => {
1949                self.show_caret ^= true;
1950            }
1951        });
1952    }
1953
1954    // Use custom drawing for the textbox so a transform can be applied to just the text.
1955    fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
1956        cx.draw_shadows(canvas);
1957        cx.draw_background(canvas);
1958        cx.draw_border(canvas);
1959        cx.draw_outline(canvas);
1960
1961        // Clip only the text content to the textbox shape so long text is contained
1962        // without clipping outside effects such as outlines.
1963        canvas.save();
1964        let path = cx.path();
1965        canvas.clip_path(&path, ClipOp::Intersect, true);
1966
1967        let bounds = cx.bounds();
1968        let padding_left = match cx.padding_left() {
1969            Units::Pixels(val) => val,
1970            _ => 0.0,
1971        };
1972        let padding_right = match cx.padding_right() {
1973            Units::Pixels(val) => val,
1974            _ => 0.0,
1975        };
1976        let padding_top = match cx.padding_top() {
1977            Units::Pixels(val) => val,
1978            _ => 0.0,
1979        };
1980        let padding_bottom = match cx.padding_bottom() {
1981            Units::Pixels(val) => val,
1982            _ => 0.0,
1983        };
1984
1985        let content_left = bounds.x + padding_left;
1986        let content_top = bounds.y + padding_top;
1987        let content_right = (bounds.x + bounds.w - padding_right).max(content_left);
1988        let content_bottom = (bounds.y + bounds.h - padding_bottom).max(content_top);
1989        canvas.clip_rect(
1990            Rect::new(content_left, content_top, content_right, content_bottom),
1991            ClipOp::Intersect,
1992            true,
1993        );
1994
1995        let transform = *self.transform.borrow();
1996        canvas.translate((transform.0, transform.1));
1997        cx.draw_text(canvas);
1998
1999        if self.edit {
2000            self.draw_selection(cx, canvas);
2001            self.draw_text_caret(cx, canvas);
2002        }
2003
2004        canvas.restore();
2005    }
2006}