Skip to main content

vizia_core/views/
textbox.rs

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