Skip to main content

ferroui_controls/
text_box.rs

1use crate::presenters::TextPresenter;
2use crate::primitives::{
3    ScrollBarVisibility, TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl,
4};
5use crate::text_box_text_input_method_client::TextBoxTextInputMethodClient;
6use crate::utils::{ClipboardHelper, IUndoRedoHost, PrimarySelectionHelper, StringUtils, UndoRedoHelper};
7use crate::{
8    Application, Border, ContentControl, ControlImpl, Decorator, PastingFromClipboardEventArgs, ScrollViewer,
9    SelectableTextBlock, TextBlock, TextChangedEventArgs, TextChangingEventArgs, TopLevel,
10};
11use ferroui_base::input::platform::{ClipboardError, ClipboardExtensions, ClipboardType, IClipboard};
12use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
13use std::any::Any;
14use ferroui_base::animation::TimeSpan;
15use ferroui_base::data::{BindingMode, BindingPriority};
16use ferroui_base::input::{
17    ContextRequestedEventArgs, FocusChangedEventArgs, HoldingRoutedEventArgs, HoldingState, InputElement,
18    InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyGesture, KeyModifiers, MouseButton, NavigationMethod,
19    PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs, PointerType, TappedEventArgs,
20    TextInputEventArgs,
21};
22use ferroui_base::interactivity::{
23    IRoutedEventArgs, Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
24};
25use ferroui_base::layout::{HorizontalAlignment, Layoutable, LayoutableImpl, LayoutableImplExt, VerticalAlignment};
26use ferroui_base::media::text_formatting::unicode::GraphemeEnumerator;
27use ferroui_base::media::text_formatting::{
28    GenericTextParagraphProperties, GenericTextRunProperties, ITextSource, LogicalDirection, TextEndOfLine,
29    TextLayout, TextParagraphProperties, TextRun, TextRunProperties,
30};
31use ferroui_base::media::{
32    BaselineAlignment, CharacterHit, FlowDirection, IBrush, TextAlignment, TextWrapping, Typeface,
33};
34use ferroui_base::platform::IPlatformSettings;
35use ferroui_base::reactive::{CompositeDisposable, IDisposable, IObserver, Observable, ObservableExt};
36use ferroui_base::threading::{Dispatcher, DispatcherPriority, DispatcherTask};
37use ferroui_base::utilities::MathUtilities;
38use ferroui_base::{
39    ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, AttachedProperty, BoxedValue,
40    DirectProperty, FerroLocator, FerroObject, LocatorExtensions, VisualImplExt, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt,
41    FerroProperty, FerroPropertyChangedEventArgs, Point, Rect, Ref, Size, StyledElementImpl, StyledProperty,
42    StyledPropertyMetadata, StyledPropertyOptions, Thickness, Visual, VisualImpl, VisualTreeAttachmentEventArgs,
43    WeakRef,
44};
45use std::cell::{Cell, OnceCell, RefCell};
46use std::rc::Rc;
47
48/// Stores the state information for the available actions of the undo/redo
49/// helper.
50///
51/// Two states are equal when their texts are: the caret position does not
52/// take part in the comparison.
53#[derive(Clone, Debug)]
54pub(crate) struct UndoRedoState {
55    text: Option<String>,
56    caret_position: i32,
57}
58
59impl UndoRedoState {
60    fn new(text: Option<String>, caret_position: i32) -> Self {
61        Self { text, caret_position }
62    }
63}
64
65impl PartialEq for UndoRedoState {
66    fn eq(&self, other: &Self) -> bool {
67        self.text == other.text
68    }
69}
70
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72enum TextMutationKind {
73    ExternalReplacement,
74    Edit,
75    InternalSynchronization,
76}
77
78/// The characters removed from every text input.
79const INVALID_CHARACTERS: [u16; 1] = [0x007f];
80
81const MAX_CHARS_BEFORE_UNDO_SNAPSHOT: i32 = 7;
82
83const CARRIAGE_RETURN: u16 = b'\r' as u16;
84const LINE_FEED: u16 = b'\n' as u16;
85const SPACE: u16 = b' ' as u16;
86
87/// Represents a control that can be used to display or edit unformatted
88/// text.
89///
90/// Template parts: `PART_TextPresenter` (a [`TextPresenter`], required) and
91/// `PART_ScrollViewer` (a [`ScrollViewer`]). Pseudo-classes: `:empty`,
92/// `:touch-mode`.
93///
94/// Every text position (`CaretIndex`, `SelectionStart`, `SelectionEnd`,
95/// `MaxLength`) is a UTF-16 code unit index.
96#[repr(C)]
97pub struct TextBox {
98    base: TemplatedControl,
99    /// The radius for touch input. Used to determine if the selection should
100    /// change from moving a touch pointer.
101    touch_radius: OnceCell<i32>,
102    presenter: RefCell<Option<Ref<TextPresenter>>>,
103    scroll_viewer: RefCell<Option<Ref<ScrollViewer>>>,
104    im_client: Rc<TextBoxTextInputMethodClient>,
105    undo_redo_helper: UndoRedoHelper<UndoRedoState>,
106    /// The handle through which the undo/redo helper reaches this text box.
107    undo_redo_host: RefCell<Option<Rc<dyn IUndoRedoHost<UndoRedoState>>>>,
108    is_undoing_redoing: Cell<bool>,
109    text_mutation_kind: Cell<TextMutationKind>,
110    // Coercion runs before the new value is committed, so a snapshot taken there would capture the old text.
111    needs_undo_redo_snapshot_after_text_change: Cell<bool>,
112    can_cut: Cell<bool>,
113    can_copy: Cell<bool>,
114    can_paste: Cell<bool>,
115    can_undo: Cell<bool>,
116    can_redo: Cell<bool>,
117
118    word_selection_start: Cell<i32>,
119    selection_at_pointer_press: Cell<(i32, i32)>,
120    selected_text_changes_made_since_last_undo_snapshot: Cell<i32>,
121    has_done_snapshot_once: Cell<bool>,
122    current_click_count: Cell<i32>,
123    is_double_tapped: Cell<bool>,
124    is_in_touch_mode: Cell<bool>,
125    last_point: Cell<Point>,
126    is_in_touch_selection_mode: Cell<bool>,
127    is_in_touch_caret_mode: Cell<bool>,
128    has_touch_selection: Cell<bool>,
129    /// The subscription of `presenter_property_changed` to the presenter.
130    presenter_property_changed: RefCell<Option<Rc<dyn IDisposable>>>,
131}
132
133ferro_class! {
134    TextBox: TemplatedControl, virtuals TextBoxImpl: TemplatedControlImpl {
135        /// Coerces the current text.
136        ///
137        /// This method also manages the internal undo/redo state whenever
138        /// the text changes: if overridden, ensure that the base is called
139        /// or undo/redo won't work correctly.
140        fn coerce_text(this, value: Option<String>) -> Option<String>;
141    }
142}
143ferroui_base::ferro_class_info!(TextBox { new: TextBox::new });
144
145ferro_impl_classes!(TextBox: StyledElementImpl, InteractiveImpl);
146
147impl ControlImpl for TextBox {
148    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
149        crate::automation::peers::TextBoxAutomationPeer::new(this).upcast()
150    }
151}
152
153impl FerroObjectImpl for TextBox {
154    fn constructed(this: &Self) {
155        // The static constructor runs before the instance constructors.
156        Self::parent_constructed(this);
157
158        let object: &FerroObject = this;
159        let accepts_return = FerroObjectExtensions::get_observable(object, Self::accepts_return_property());
160        let text_wrapping = FerroObjectExtensions::get_observable(object, Self::text_wrapping_property());
161
162        // The latest value of each property combined into the visibility of
163        // the horizontal scroll bar, once both have produced a value.
164        let horizontal_scroll_bar_visibility =
165            Observable::create(move |observer: Rc<dyn IObserver<ScrollBarVisibility>>| {
166                let latest: Rc<(Cell<Option<bool>>, Cell<Option<TextWrapping>>)> =
167                    Rc::new((Cell::new(None), Cell::new(None)));
168
169                let publish = {
170                    let latest = latest.clone();
171                    move || {
172                        if let (Some(accepts_return), Some(wrapping)) = (latest.0.get(), latest.1.get()) {
173                            observer.on_next(if wrapping != TextWrapping::NoWrap {
174                                ScrollBarVisibility::Disabled
175                            } else if accepts_return {
176                                ScrollBarVisibility::Auto
177                            } else {
178                                ScrollBarVisibility::Hidden
179                            });
180                        }
181                    }
182                };
183
184                let accepts_return = {
185                    let (latest, publish) = (latest.clone(), publish.clone());
186                    accepts_return.subscribe_fn(move |value| {
187                        latest.0.set(Some(value));
188                        publish();
189                    })
190                };
191
192                let text_wrapping = text_wrapping.subscribe_fn(move |value| {
193                    latest.1.set(Some(value));
194                    publish();
195                });
196
197                let subscription: Rc<dyn IDisposable> =
198                    Rc::new(CompositeDisposable::from_disposables([accepts_return, text_wrapping]));
199                subscription
200            });
201
202        FerroObjectExtensions::bind_typed(
203            object,
204            ScrollViewer::horizontal_scroll_bar_visibility_property(),
205            horizontal_scroll_bar_visibility,
206            BindingPriority::Style,
207        );
208
209        let host: Rc<dyn IUndoRedoHost<UndoRedoState>> = Rc::new(TextBoxUndoRedoHost(this.to_ref().downgrade()));
210        this.undo_redo_helper.set_host(Rc::downgrade(&host));
211        *this.undo_redo_host.borrow_mut() = Some(host);
212
213        this.selected_text_changes_made_since_last_undo_snapshot.set(0);
214        this.has_done_snapshot_once.set(false);
215        this.update_command_states();
216        this.update_pseudoclasses();
217    }
218
219    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
220        Self::parent_on_property_changed(this, change);
221
222        let property = change.property();
223
224        if property == Self::text_property().as_property() {
225            // Do not expose a selection with only one endpoint coerced to the new text.
226            let _change = this.im_client.begin_change();
227
228            if this.needs_undo_redo_snapshot_after_text_change.get() {
229                this.needs_undo_redo_snapshot_after_text_change.set(false);
230                this.snapshot_undo_redo(true);
231            }
232
233            this.coerce_value(Self::caret_index_property().as_property());
234            this.coerce_value(Self::selection_start_property().as_property());
235            this.coerce_value(Self::selection_end_property().as_property());
236
237            this.raise_text_change_events();
238
239            this.update_pseudoclasses();
240            this.update_command_states();
241        } else if property == Self::is_read_only_property().as_property()
242            || property == Self::password_char_property().as_property()
243            || property == Self::reveal_password_property().as_property()
244        {
245            this.update_command_states();
246        } else if property == Self::caret_index_property().as_property() {
247            this.on_caret_index_changed(change);
248        } else if property == Self::selection_start_property().as_property() {
249            this.on_selection_start_changed(change);
250        } else if property == Self::selection_end_property().as_property() {
251            this.on_selection_end_changed(change);
252        } else if property == Self::max_lines_property().as_property() {
253            this.invalidate_measure();
254        } else if property == Self::min_lines_property().as_property() {
255            this.invalidate_measure();
256        } else if property == Self::undo_limit_property().as_property() {
257            this.on_undo_limit_changed(change.get_new_value::<i32>());
258        } else if property == Self::is_undo_enabled_property().as_property() && !change.get_new_value::<bool>() {
259            // "Setting this property to false clears the undo stack.
260            // Therefore, if you disable undo and then re-enable it, undo commands still do not work
261            // because the undo stack was emptied when you disabled undo."
262            this.clear_undo_redo();
263        }
264    }
265}
266
267impl VisualImpl for TextBox {
268    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
269        Self::parent_on_attached_to_visual_tree(this, e);
270
271        if let Some(presenter) = this.presenter() {
272            if this.is_focused() {
273                presenter.show_caret();
274            } else if this.is_inactive_selection_highlight_enabled() {
275                presenter.set_show_selection_highlight(true);
276            }
277
278            let weak = this.to_ref().downgrade();
279            let subscription = presenter.property_changed(move |e| {
280                if let Some(this) = weak.upgrade() {
281                    this.presenter_property_changed(e);
282                }
283            });
284
285            let previous = this.presenter_property_changed.borrow_mut().replace(subscription);
286
287            if let Some(previous) = previous {
288                previous.dispose();
289            }
290        }
291    }
292
293    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
294        Self::parent_on_detached_from_visual_tree(this, e);
295
296        if let Some(presenter) = this.presenter() {
297            presenter.hide_caret();
298
299            let subscription = this.presenter_property_changed.borrow_mut().take();
300
301            if let Some(subscription) = subscription {
302                subscription.dispose();
303            }
304        }
305
306        this.im_client.set_presenter(None, None);
307    }
308}
309
310impl LayoutableImpl for TextBox {
311    fn measure_override(this: &Self, available_size: Size) -> Size {
312        if let Some(scroll_viewer) = this.scroll_viewer() {
313            let mut max_height = f64::INFINITY;
314
315            let max_lines = this.max_lines();
316
317            if max_lines > 0 && this.height().is_nan() {
318                let text_layout = this.create_lines_text_layout(max_lines);
319                let vertical_space = this.get_vertical_space_between_scroll_viewer_and_presenter();
320
321                max_height = (text_layout.height() + vertical_space).ceil();
322            }
323
324            scroll_viewer.set_current_value(Layoutable::max_height_property(), max_height);
325
326            let mut min_height = 0.0;
327
328            let min_lines = this.min_lines();
329
330            if min_lines > 0 && this.height().is_nan() {
331                let text_layout = this.create_lines_text_layout(min_lines);
332                let vertical_space = this.get_vertical_space_between_scroll_viewer_and_presenter();
333
334                min_height = (text_layout.height() + vertical_space).ceil();
335            }
336
337            scroll_viewer.set_current_value(Layoutable::min_height_property(), min_height);
338        }
339
340        Self::parent_measure_override(this, available_size)
341    }
342}
343
344impl InputElementImpl for TextBox {
345    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
346        Self::parent_on_got_focus(this, e);
347
348        let presenter = this.presenter();
349
350        if let Some(presenter) = &presenter {
351            presenter.set_show_selection_highlight(true);
352        }
353
354        // When navigating to a text box via the tab key, select all text if
355        //   1) this text box is *not* a multiline text box
356        //   2) this text box has any text to select
357        if e.navigation_method == NavigationMethod::Tab
358            && !this.accepts_return()
359            && this.text().is_some_and(|text| !text.is_empty())
360        {
361            this.select_all();
362        }
363
364        this.update_command_states();
365
366        let presenter = this.presenter();
367
368        this.im_client.set_presenter(presenter.as_ref(), Some(&this.to_ref()));
369
370        if let Some(presenter) = &presenter {
371            presenter.show_caret();
372        }
373
374        if this.selection_start() != this.selection_end() {
375            if let Some(canvas) = presenter.as_ref().and_then(|presenter| presenter.text_selection_handle_canvas()) {
376                canvas.show(false);
377            }
378        }
379    }
380
381    fn on_lost_focus(this: &Self, e: &FocusChangedEventArgs) {
382        Self::parent_on_lost_focus(this, e);
383
384        if !this.context_flyout().is_some_and(|flyout| flyout.is_open())
385            && !this.context_menu().is_some_and(|menu| menu.is_open())
386        {
387            if this.clear_selection_on_lost_focus() {
388                this.clear_selection();
389            }
390
391            this.set_current_value(Self::reveal_password_property(), false);
392            if let Some(presenter) = this.presenter() {
393                presenter.remove_text_selection_canvas();
394            }
395        }
396
397        this.update_command_states();
398
399        let presenter = this.presenter();
400
401        if let Some(presenter) = &presenter {
402            presenter.hide_caret();
403        }
404
405        this.im_client.set_presenter(None, None);
406
407        if let Some(presenter) = &presenter {
408            if !this.is_inactive_selection_highlight_enabled() {
409                presenter.set_show_selection_highlight(false);
410            }
411        }
412    }
413
414    fn on_text_input(this: &Self, e: &TextInputEventArgs) {
415        if !e.handled() {
416            this.handle_text_input(e.text.as_deref());
417            e.set_handled(true);
418        }
419    }
420
421    fn on_key_down(this: &Self, e: &KeyEventArgs) {
422        let Some(presenter) = this.presenter() else {
423            return;
424        };
425
426        if presenter.preedit_text().is_some_and(|preedit_text| !preedit_text.is_empty()) {
427            return;
428        }
429
430        let mut text = this.text_utf16();
431        let caret_index = this.caret_index();
432        let mut movement = false;
433        let mut selection = false;
434        let mut handled = false;
435        let modifiers = e.key_modifiers;
436
437        let keymap = Application::current()
438            .expect("the application")
439            .platform_settings()
440            .expect("the platform settings of the application")
441            .hotkey_configuration();
442
443        let _change = this.im_client.begin_change();
444
445        let matches = |gestures: &[KeyGesture]| gestures.iter().any(|g| g.matches(Some(e)));
446        let detect_selection = || e.key_modifiers.contains(keymap.selection_modifiers);
447
448        if matches(&keymap.select_all) {
449            this.select_all();
450            handled = true;
451        } else if matches(&keymap.copy) {
452            if this.can_copy() {
453                this.copy();
454            }
455
456            handled = true;
457        } else if matches(&keymap.cut) {
458            if this.can_cut() {
459                this.cut();
460            }
461
462            handled = true;
463        } else if matches(&keymap.paste) {
464            if this.can_paste() {
465                this.paste();
466            }
467
468            handled = true;
469        } else if matches(&keymap.undo) && this.is_undo_enabled() {
470            if !this.is_read_only() {
471                this.undo();
472            }
473
474            handled = true;
475        } else if matches(&keymap.redo) && this.is_undo_enabled() {
476            if !this.is_read_only() {
477                this.redo();
478            }
479
480            handled = true;
481        } else if matches(&keymap.move_cursor_to_the_start_of_document) {
482            this.move_home(true);
483            movement = true;
484            selection = false;
485            handled = true;
486            this.set_current_value(Self::caret_index_property(), presenter.caret_index());
487        } else if matches(&keymap.move_cursor_to_the_end_of_document) {
488            this.move_end(true);
489            movement = true;
490            selection = false;
491            handled = true;
492            this.set_current_value(Self::caret_index_property(), presenter.caret_index());
493        } else if matches(&keymap.move_cursor_to_the_start_of_line) {
494            this.move_home(false);
495            movement = true;
496            selection = false;
497            handled = true;
498            this.set_current_value(Self::caret_index_property(), presenter.caret_index());
499        } else if matches(&keymap.move_cursor_to_the_end_of_line) {
500            this.move_end(false);
501            movement = true;
502            selection = false;
503            handled = true;
504            this.set_current_value(Self::caret_index_property(), presenter.caret_index());
505        } else if matches(&keymap.move_cursor_to_the_start_of_document_with_selection) {
506            this.set_current_value(Self::selection_start_property(), caret_index);
507            this.move_home(true);
508            this.set_current_value(Self::selection_end_property(), presenter.caret_index());
509            movement = true;
510            selection = true;
511            handled = true;
512        } else if matches(&keymap.move_cursor_to_the_end_of_document_with_selection) {
513            this.set_current_value(Self::selection_start_property(), caret_index);
514            this.move_end(true);
515            this.set_current_value(Self::selection_end_property(), presenter.caret_index());
516            movement = true;
517            selection = true;
518            handled = true;
519        } else if matches(&keymap.move_cursor_to_the_start_of_line_with_selection) {
520            this.set_current_value(Self::selection_start_property(), caret_index);
521            this.move_home(false);
522            this.set_current_value(Self::selection_end_property(), presenter.caret_index());
523            movement = true;
524            selection = true;
525            handled = true;
526        } else if matches(&keymap.move_cursor_to_the_end_of_line_with_selection) {
527            this.set_current_value(Self::selection_start_property(), caret_index);
528            this.move_end(false);
529            this.set_current_value(Self::selection_end_property(), presenter.caret_index());
530            movement = true;
531            selection = true;
532            handled = true;
533        } else if matches(&keymap.page_left) {
534            this.move_page_left();
535            movement = true;
536            selection = false;
537            handled = true;
538        } else if matches(&keymap.page_right) {
539            this.move_page_right();
540            movement = true;
541            selection = false;
542            handled = true;
543        } else if matches(&keymap.page_up) {
544            this.move_page_up();
545            movement = true;
546            selection = false;
547            handled = true;
548        } else if matches(&keymap.page_down) {
549            this.move_page_down();
550            movement = true;
551            selection = false;
552            handled = true;
553        } else {
554            // It's not secure to rely on password field content when moving.
555            let has_whole_word_modifiers =
556                modifiers.contains(keymap.whole_word_text_action_modifiers) && !this.is_password_box();
557
558            match e.key {
559                Key::Left => {
560                    selection = detect_selection();
561                    this.move_horizontal(-1, has_whole_word_modifiers, selection, true);
562                    if caret_index != presenter.caret_index() {
563                        movement = true;
564                    }
565                }
566
567                Key::Right => {
568                    selection = detect_selection();
569                    this.move_horizontal(1, has_whole_word_modifiers, selection, true);
570                    if caret_index != presenter.caret_index() {
571                        movement = true;
572                    }
573                }
574
575                Key::Up => {
576                    selection = detect_selection();
577                    this.move_vertical(LogicalDirection::Backward, selection);
578                    if caret_index != presenter.caret_index() {
579                        movement = true;
580                    }
581                }
582
583                Key::Down => {
584                    selection = detect_selection();
585                    this.move_vertical(LogicalDirection::Forward, selection);
586                    if caret_index != presenter.caret_index() {
587                        movement = true;
588                    }
589                }
590
591                Key::Back => {
592                    if !this.is_read_only() {
593                        this.snapshot_undo_redo(true);
594
595                        if has_whole_word_modifiers && this.selection_start() == this.selection_end() {
596                            this.set_selection_for_control_backspace();
597                        }
598
599                        if !this.delete_selection() {
600                            let character_hit = presenter.get_next_character_hit(LogicalDirection::Backward);
601
602                            let mut backspace_position =
603                                character_hit.first_character_index() + character_hit.trailing_length();
604
605                            let text_layout = presenter.text_layout();
606
607                            let line_index = text_layout.get_line_index_from_character_index(caret_index, true);
608
609                            let backspace_character_hit = text_layout.text_lines()[line_index as usize]
610                                .get_backspace_caret_character_hit(CharacterHit::new(caret_index));
611
612                            if backspace_character_hit.first_character_index() > backspace_position
613                                && backspace_character_hit.first_character_index() < caret_index
614                            {
615                                backspace_position = backspace_character_hit.first_character_index();
616                            }
617
618                            if caret_index != backspace_position {
619                                let start = backspace_position.min(caret_index);
620                                let end = backspace_position.max(caret_index);
621
622                                remove_range(&mut text, start, end - start);
623
624                                this.set_text_from_edit(Some(String::from_utf16_lossy(&text)));
625
626                                this.set_current_value(Self::caret_index_property(), start);
627
628                                presenter.move_caret_to_text_position(start, false);
629                            }
630                        }
631
632                        this.snapshot_undo_redo(true);
633                    }
634
635                    handled = true;
636                }
637
638                Key::Delete => {
639                    if !this.is_read_only() {
640                        this.snapshot_undo_redo(true);
641
642                        if has_whole_word_modifiers && this.selection_start() == this.selection_end() {
643                            this.set_selection_for_control_delete();
644                        }
645
646                        if !this.delete_selection() {
647                            let character_hit = presenter.get_next_character_hit(LogicalDirection::Forward);
648
649                            let next_position =
650                                character_hit.first_character_index() + character_hit.trailing_length();
651
652                            if next_position != caret_index {
653                                let start = next_position.min(caret_index);
654                                let end = next_position.max(caret_index);
655
656                                remove_range(&mut text, start, end - start);
657
658                                this.set_text_from_edit(Some(String::from_utf16_lossy(&text)));
659                            }
660                        }
661
662                        this.snapshot_undo_redo(true);
663                    }
664
665                    handled = true;
666                }
667
668                Key::Enter => {
669                    if this.accepts_return() {
670                        if !this.is_read_only() {
671                            this.snapshot_undo_redo(true);
672                            this.handle_text_input(Some(&this.new_line()));
673                        }
674
675                        handled = true;
676                    }
677                }
678
679                Key::Tab => {
680                    if this.accepts_tab() {
681                        if !this.is_read_only() {
682                            this.snapshot_undo_redo(true);
683                            this.handle_text_input(Some("\t"));
684                        }
685
686                        handled = true;
687                    } else {
688                        Self::parent_on_key_down(this, e);
689                    }
690                }
691
692                Key::Space => {
693                    if !this.is_read_only() {
694                        this.snapshot_undo_redo(true); // always snapshot in between words
695                    }
696                }
697
698                _ => {
699                    handled = false;
700                }
701            }
702        }
703
704        if movement && !selection {
705            this.clear_selection();
706        }
707
708        if handled || movement {
709            e.set_handled(true);
710        }
711    }
712
713    fn on_holding(this: &Self, e: &HoldingRoutedEventArgs) {
714        Self::parent_on_holding(this, e);
715
716        let presenter = match this.presenter() {
717            Some(presenter) if e.holding_state() == HoldingState::Started => presenter,
718            _ => {
719                this.is_in_touch_selection_mode.set(e.holding_state() == HoldingState::Canceled);
720                this.has_touch_selection.set(false);
721                return;
722            }
723        };
724
725        let text = this.text();
726
727        let _change = this.im_client.begin_change();
728
729        if let Some(text) = text {
730            let text: Vec<u16> = text.encode_utf16().collect();
731            let position = e.pointer_event_args().get_position(Some(&presenter));
732            let selection_start = this.selection_start();
733            let selection_end = this.selection_end();
734            presenter.move_caret_to_point(position);
735            let caret_index = presenter.caret_index();
736            let is_in_selection =
737                selection_start != selection_end && caret_index >= selection_start && caret_index <= selection_end;
738
739            if is_in_selection {
740                presenter.raise_event(&ContextRequestedEventArgs::from_pointer_event_args(e.pointer_event_args()));
741            } else {
742                // We select the current held word, or the whole hidden content.
743                if this.is_password_box() {
744                    this.word_selection_start.set(-1);
745
746                    this.select_all();
747                } else {
748                    this.select_word(&text, caret_index, caret_index, caret_index);
749                }
750
751                presenter.ensure_text_selection_layer();
752
753                if this.selection_start() != this.selection_end() {
754                    if let Some(canvas) = presenter.text_selection_handle_canvas() {
755                        canvas.show(true);
756                    }
757                } else {
758                    presenter
759                        .raise_event(&ContextRequestedEventArgs::from_pointer_event_args(e.pointer_event_args()));
760                }
761            }
762
763            this.has_touch_selection.set(true);
764
765            e.set_handled(true);
766            let element: &InputElement = this;
767            crate::platform::PlatformFeedbackExtensions::perform_feedback(element, crate::platform::FeedbackAction::hold());
768        }
769    }
770
771    fn on_tapped(this: &Self, e: &TappedEventArgs) {
772        Self::parent_on_tapped(this, e);
773
774        if e.pointer().type_() != PointerType::Mouse {
775            if let Some(presenter) = this.presenter() {
776                presenter.ensure_text_selection_layer();
777                if let Some(canvas) = presenter.text_selection_handle_canvas() {
778                    canvas.show(false);
779                }
780            }
781        }
782    }
783
784    fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
785        let Some(presenter) = this.presenter() else {
786            return;
787        };
788
789        let text = this.text();
790        let click_info = e.get_current_point(Some(this));
791
792        let _change = this.im_client.begin_change();
793
794        this.is_in_touch_mode.set(false);
795        this.is_in_touch_selection_mode.set(false);
796        this.is_double_tapped.set(e.click_count() == 2);
797        this.selection_at_pointer_press.set(this.get_selection_range());
798
799        if let Some(text) = text.filter(|_| !click_info.pointer.captured().is_some_and(|c| c.is::<Border>())) {
800            let text: Vec<u16> = text.encode_utf16().collect();
801
802            if e.pointer().type_() == PointerType::Mouse && click_info.properties.is_left_button_pressed {
803                if let Some(canvas) = presenter.text_selection_handle_canvas() {
804                    canvas.hide();
805                }
806                this.current_click_count.set(e.click_count());
807                let point = e.get_position(Some(&presenter));
808
809                presenter.move_caret_to_point(point);
810
811                let caret_index = presenter.caret_index();
812                let click_to_select = e.key_modifiers().contains(KeyModifiers::SHIFT);
813                let mut selection_start = this.selection_start();
814                let mut selection_end = this.selection_end();
815
816                match e.click_count() {
817                    1 => {
818                        if click_to_select {
819                            if this.word_selection_start.get() >= 0 {
820                                this.update_word_selection_range(
821                                    caret_index,
822                                    &mut selection_start,
823                                    &mut selection_end,
824                                );
825
826                                this.set_current_value(Self::selection_start_property(), selection_start);
827                                this.set_current_value(Self::selection_end_property(), selection_end);
828                            } else {
829                                this.set_current_value(Self::selection_end_property(), caret_index);
830                            }
831                        } else {
832                            this.set_current_value(Self::selection_start_property(), caret_index);
833                            this.set_current_value(Self::selection_end_property(), caret_index);
834                            this.word_selection_start.set(-1);
835                        }
836                    }
837                    2 => {
838                        this.select_word(&text, caret_index, selection_start, selection_end);
839                    }
840                    3 => {
841                        this.word_selection_start.set(-1);
842
843                        this.select_all();
844                    }
845                    _ => {}
846                }
847            } else if e.pointer().type_() != PointerType::Mouse {
848                this.is_in_touch_mode.set(true);
849                this.last_point.set(e.get_current_point(Some(&presenter)).position);
850
851                if this.is_double_tapped.get() {
852                    presenter.move_caret_to_point(this.last_point.get());
853                    let caret_index = presenter.caret_index();
854
855                    let selection_start = this.selection_start();
856                    let selection_end = this.selection_end();
857
858                    this.select_word(&text, caret_index, selection_start, selection_end);
859                    presenter.ensure_text_selection_layer();
860                    if let Some(canvas) = presenter.text_selection_handle_canvas() {
861                        canvas.show(false);
862                    }
863                }
864            }
865        }
866
867        this.update_pseudoclasses();
868
869        let element: Ref<InputElement> = presenter.upcast();
870        e.pointer().capture(Some(&element));
871        e.set_handled(true);
872    }
873
874    fn on_pointer_moved(this: &Self, e: &PointerEventArgs) {
875        let Some(presenter) = this.presenter().filter(|presenter| is_captured_by(e, presenter)) else {
876            return;
877        };
878
879        let _change = this.im_client.begin_change();
880        let mut point = e.get_position(Some(&presenter));
881
882        if e.pointer().type_() == PointerType::Mouse {
883            // The selection should not change during a pointer move if the user right clicks.
884            if is_captured_by(e, &presenter) && e.get_current_point(Some(this)).properties.is_left_button_pressed {
885                point = clamp_to_bounds(point, &presenter);
886
887                let previous_index = presenter.caret_index();
888
889                presenter.move_caret_to_point(point);
890
891                let caret_index = presenter.caret_index();
892
893                if (caret_index - previous_index).abs() == 1 {
894                    e.prevent_gesture_recognition();
895                }
896
897                if e.pointer().type_() == PointerType::Mouse || this.is_double_tapped.get() {
898                    let mut selection_start = this.selection_start();
899                    let mut selection_end = this.selection_end();
900
901                    if this.word_selection_start.get() >= 0 {
902                        this.update_word_selection_range(caret_index, &mut selection_start, &mut selection_end);
903
904                        this.set_current_value(Self::selection_start_property(), selection_start);
905                        this.set_current_value(Self::selection_end_property(), selection_end);
906                    } else {
907                        this.set_current_value(Self::selection_end_property(), caret_index);
908                    }
909                } else {
910                    this.set_current_value(Self::selection_start_property(), caret_index);
911                    this.set_current_value(Self::selection_end_property(), caret_index);
912                }
913            }
914        } else if this.is_in_touch_mode.get() {
915            if this.is_in_touch_selection_mode.get() {
916                point = clamp_to_bounds(point, &presenter);
917
918                let previous_index = presenter.caret_index();
919
920                presenter.move_caret_to_point(point);
921
922                let caret_index = presenter.caret_index();
923
924                if (caret_index - previous_index).abs() == 1 {
925                    e.prevent_gesture_recognition();
926                }
927
928                let mut selection_start = this.selection_start();
929                let mut selection_end = this.selection_end();
930
931                if this.word_selection_start.get() >= 0 {
932                    this.update_word_selection_range(caret_index, &mut selection_start, &mut selection_end);
933
934                    this.set_current_value(Self::selection_start_property(), selection_start);
935                    this.set_current_value(Self::selection_end_property(), selection_end);
936                } else {
937                    this.set_current_value(Self::selection_end_property(), caret_index);
938                }
939            } else {
940                if !this.is_in_touch_caret_mode.get() {
941                    let last_point = this.last_point.get();
942                    let touch_rect =
943                        Rect::new(last_point.x, last_point.y, 0.0, 0.0).inflate(f64::from(this.touch_radius()));
944                    let is_in_rect = touch_rect.x < point.x
945                        && touch_rect.y < point.y
946                        && touch_rect.right() > point.x
947                        && touch_rect.bottom() > point.y;
948
949                    if !is_in_rect {
950                        this.is_in_touch_caret_mode.set(true);
951                    }
952                }
953
954                if this.is_in_touch_caret_mode.get() {
955                    e.prevent_gesture_recognition();
956                    presenter.move_caret_to_point(point);
957                    let caret_index = presenter.caret_index();
958                    this.set_current_value(Self::selection_start_property(), caret_index);
959                    this.set_current_value(Self::selection_end_property(), caret_index);
960                }
961            }
962        }
963    }
964
965    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
966        let Some(presenter) = this.presenter() else {
967            return;
968        };
969
970        if !is_captured_by(e, &presenter) {
971            return;
972        }
973
974        let _change = this.im_client.begin_change();
975
976        if e.pointer().type_() != PointerType::Mouse
977            && !this.is_in_touch_selection_mode.get()
978            && !this.is_double_tapped.get()
979            && !this.has_touch_selection.get()
980        {
981            let point = e.get_position(Some(&presenter));
982
983            presenter.move_caret_to_point(point);
984
985            let caret_index = presenter.caret_index();
986            this.set_current_value(Self::caret_index_property(), caret_index);
987            this.set_current_value(Self::selection_end_property(), caret_index);
988            this.set_current_value(Self::selection_start_property(), caret_index);
989        }
990
991        if e.initial_press_mouse_button() == MouseButton::Right {
992            let point = e.get_position(Some(&presenter));
993
994            presenter.move_caret_to_point(point);
995
996            let caret_index = presenter.caret_index();
997
998            // See if the mouse clicked inside the current selection:
999            // if it did not, the selection moves to where the user clicked.
1000            let first_selection = this.selection_start().min(this.selection_end());
1001            let last_selection = this.selection_start().max(this.selection_end());
1002            let did_click_in_selection = this.selection_start() != this.selection_end()
1003                && caret_index >= first_selection
1004                && caret_index <= last_selection;
1005            if !did_click_in_selection {
1006                this.set_current_value(Self::caret_index_property(), caret_index);
1007                this.set_current_value(Self::selection_end_property(), caret_index);
1008                this.set_current_value(Self::selection_start_property(), caret_index);
1009            }
1010        }
1011
1012        if e.initial_press_mouse_button() == MouseButton::Middle {
1013            // Middle-click pastes the primary selection at the click position on platforms supporting it.
1014            if !this.is_read_only() {
1015                if let Some(primary_selection) = TopLevel::get_top_level(Some(this))
1016                    .and_then(|top_level| top_level.try_get_clipboard(ClipboardType::PrimarySelection))
1017                {
1018                    presenter.move_caret_to_point(e.get_position(Some(&presenter)));
1019
1020                    let caret_index = presenter.caret_index();
1021                    this.set_current_value(Self::caret_index_property(), caret_index);
1022                    this.set_current_value(Self::selection_start_property(), caret_index);
1023                    this.set_current_value(Self::selection_end_property(), caret_index);
1024
1025                    // Nobody observes the outcome of this paste: a failure it
1026                    // does not log stays in the dropped task.
1027                    drop(this.paste_core_async(Some(primary_selection)));
1028                    e.set_handled(true);
1029                }
1030            }
1031        } else if e.initial_press_mouse_button() == MouseButton::Left {
1032            let selection = this.get_selection_range();
1033            if !this.is_password_box()
1034                && selection.0 != selection.1
1035                && selection != this.selection_at_pointer_press.get()
1036            {
1037                // The pointer gesture changed the selection, publish it to the primary selection.
1038                let target = this.to_ref();
1039                drop(PrimarySelectionHelper::publish_text_async(this, move || Some(target.get_selection())));
1040            }
1041        }
1042
1043        this.is_in_touch_selection_mode.set(false);
1044        this.is_in_touch_caret_mode.set(false);
1045        this.has_touch_selection.set(false);
1046    }
1047}
1048
1049impl TemplatedControlImpl for TextBox {
1050    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
1051        let presenter = e.name_scope().get_as::<TextPresenter>("PART_TextPresenter");
1052        *this.presenter.borrow_mut() = Some(presenter.clone());
1053
1054        let scroll_viewer = e.name_scope().find_as::<ScrollViewer>("PART_ScrollViewer");
1055        *this.scroll_viewer.borrow_mut() = scroll_viewer;
1056
1057        this.im_client.set_presenter(Some(&presenter), Some(&this.to_ref()));
1058
1059        if this.is_focused() {
1060            presenter.show_caret();
1061        }
1062    }
1063}
1064
1065impl TextBoxImpl for TextBox {
1066    fn coerce_text(this: &Self, value: Option<String>) -> Option<String> {
1067        if !this.is_undoing_redoing.get() {
1068            match this.text_mutation_kind.get() {
1069                TextMutationKind::Edit => {
1070                    this.snapshot_undo_redo(true);
1071
1072                    if !this.undo_redo_helper.can_undo() && this.text() != value {
1073                        this.needs_undo_redo_snapshot_after_text_change.set(true);
1074                    }
1075                }
1076
1077                TextMutationKind::InternalSynchronization => {}
1078
1079                TextMutationKind::ExternalReplacement => {
1080                    this.clear_undo_redo();
1081                    this.needs_undo_redo_snapshot_after_text_change.set(true);
1082                }
1083            }
1084        }
1085
1086        value
1087    }
1088}
1089
1090/// The handle through which the undo/redo helper reaches its text box.
1091struct TextBoxUndoRedoHost(WeakRef<TextBox>);
1092
1093impl IUndoRedoHost<UndoRedoState> for TextBoxUndoRedoHost {
1094    fn undo_redo_state(&self) -> UndoRedoState {
1095        match self.0.upgrade() {
1096            Some(text_box) => UndoRedoState::new(text_box.text(), text_box.caret_index()),
1097            None => UndoRedoState::new(None, 0),
1098        }
1099    }
1100
1101    fn set_undo_redo_state(&self, value: UndoRedoState) {
1102        if let Some(text_box) = self.0.upgrade() {
1103            text_box.set_current_value(TextBox::text_property(), value.text);
1104            text_box.set_current_value(TextBox::caret_index_property(), value.caret_position);
1105            text_box.clear_selection();
1106        }
1107    }
1108
1109    /// Called from the undo/redo helper when the undo stack is modified.
1110    fn on_undo_stack_changed(&self) {
1111        if let Some(text_box) = self.0.upgrade() {
1112            text_box.set_can_undo(text_box.undo_redo_helper.can_undo());
1113        }
1114    }
1115
1116    /// Called from the undo/redo helper when the redo stack is modified.
1117    fn on_redo_stack_changed(&self) {
1118        if let Some(text_box) = self.0.upgrade() {
1119            text_box.set_can_redo(text_box.undo_redo_helper.can_redo());
1120        }
1121    }
1122}
1123
1124/// The text source of a number of empty lines: it measures the height of
1125/// `MinLines` and `MaxLines`.
1126struct LineTextSource {
1127    lines: i32,
1128}
1129
1130impl ITextSource for LineTextSource {
1131    fn get_text_run(&self, text_source_index: i32) -> Option<Rc<dyn TextRun>> {
1132        if text_source_index >= self.lines {
1133            return None;
1134        }
1135
1136        Some(Rc::new(TextEndOfLine::with_length(1)))
1137    }
1138}
1139
1140impl TextBox {
1141    /// A platform-specific [`KeyGesture`] for the cut action.
1142    pub fn cut_gesture() -> Option<KeyGesture> {
1143        Application::current()?.platform_settings()?.hotkey_configuration().cut.first().cloned()
1144    }
1145
1146    /// A platform-specific [`KeyGesture`] for the copy action.
1147    pub fn copy_gesture() -> Option<KeyGesture> {
1148        Application::current()?.platform_settings()?.hotkey_configuration().copy.first().cloned()
1149    }
1150
1151    /// A platform-specific [`KeyGesture`] for the paste action.
1152    pub fn paste_gesture() -> Option<KeyGesture> {
1153        Application::current()?.platform_settings()?.hotkey_configuration().paste.first().cloned()
1154    }
1155}
1156
1157ferroui_base::ferro_properties! { impl TextBox, also [
1158    TextBox::use_floating_placeholder_property,
1159    TextBox::placeholder_foreground_property,
1160    TextBox::new_line_property,
1161    TextBox::inner_left_content_property,
1162    TextBox::inner_right_content_property,
1163    TextBox::reveal_password_property,
1164    TextBox::can_cut_property,
1165    TextBox::can_copy_property,
1166    TextBox::can_paste_property,
1167    TextBox::is_undo_enabled_property,
1168    TextBox::undo_limit_property,
1169    TextBox::can_undo_property,
1170    TextBox::can_redo_property,
1171] {
1172    ferro_property!(
1173        /// Defines the `IsInactiveSelectionHighlightEnabled` property.
1174        pub fn is_inactive_selection_highlight_enabled_property() -> StyledProperty<bool> {
1175            FerroProperty::register::<TextBox, _>("IsInactiveSelectionHighlightEnabled", true)
1176        }
1177    );
1178
1179    ferro_property!(
1180        /// Defines the `ClearSelectionOnLostFocus` property.
1181        pub fn clear_selection_on_lost_focus_property() -> StyledProperty<bool> {
1182            FerroProperty::register::<TextBox, _>("ClearSelectionOnLostFocus", true)
1183        }
1184    );
1185
1186    ferro_property!(
1187        /// Defines the `AcceptsReturn` property.
1188        pub fn accepts_return_property() -> StyledProperty<bool> {
1189            FerroProperty::register::<TextBox, _>("AcceptsReturn", false)
1190        }
1191    );
1192
1193    ferro_property!(
1194        /// Defines the `AcceptsTab` property.
1195        pub fn accepts_tab_property() -> StyledProperty<bool> {
1196            FerroProperty::register::<TextBox, _>("AcceptsTab", false)
1197        }
1198    );
1199
1200    ferro_property!(
1201        /// Defines the `CaretIndex` property.
1202        pub fn caret_index_property() -> StyledProperty<i32> {
1203            FerroProperty::register_with::<TextBox, _>(
1204                "CaretIndex",
1205                StyledPropertyOptions::new(0).coerce(Self::coerce_caret_index),
1206            )
1207        }
1208    );
1209
1210    ferro_property!(
1211        /// Defines the `IsReadOnly` property.
1212        pub fn is_read_only_property() -> StyledProperty<bool> {
1213            FerroProperty::register::<TextBox, _>("IsReadOnly", false)
1214        }
1215    );
1216
1217    ferro_property!(
1218        /// Defines the `PasswordChar` property (`'\0'`: none).
1219        pub fn password_char_property() -> StyledProperty<char> {
1220            FerroProperty::register::<TextBox, _>("PasswordChar", '\0')
1221        }
1222    );
1223
1224    ferro_property!(
1225        /// Defines the `SelectionBrush` property.
1226        pub fn selection_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
1227            FerroProperty::register::<TextBox, _>("SelectionBrush", None)
1228        }
1229    );
1230
1231    ferro_property!(
1232        /// Defines the `SelectionForegroundBrush` property.
1233        pub fn selection_foreground_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
1234            FerroProperty::register::<TextBox, _>("SelectionForegroundBrush", None)
1235        }
1236    );
1237
1238    ferro_property!(
1239        /// Defines the `CaretBrush` property.
1240        pub fn caret_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
1241            FerroProperty::register::<TextBox, _>("CaretBrush", None)
1242        }
1243    );
1244
1245    ferro_property!(
1246        /// Defines the `CaretBlinkInterval` property.
1247        pub fn caret_blink_interval_property() -> StyledProperty<TimeSpan> {
1248            FerroProperty::register::<TextBox, _>("CaretBlinkInterval", TimeSpan::from_milliseconds(500.0))
1249        }
1250    );
1251
1252    ferro_property!(
1253        /// Defines the `SelectionStart` property.
1254        pub fn selection_start_property() -> StyledProperty<i32> {
1255            FerroProperty::register_with::<TextBox, _>(
1256                "SelectionStart",
1257                StyledPropertyOptions::new(0).coerce(Self::coerce_caret_index),
1258            )
1259        }
1260    );
1261
1262    ferro_property!(
1263        /// Defines the `SelectionEnd` property.
1264        pub fn selection_end_property() -> StyledProperty<i32> {
1265            FerroProperty::register_with::<TextBox, _>(
1266                "SelectionEnd",
1267                StyledPropertyOptions::new(0).coerce(Self::coerce_caret_index),
1268            )
1269        }
1270    );
1271
1272    ferro_property!(
1273        /// Defines the `MaxLength` property.
1274        pub fn max_length_property() -> StyledProperty<i32> {
1275            FerroProperty::register::<TextBox, _>("MaxLength", 0)
1276        }
1277    );
1278
1279    ferro_property!(
1280        /// Defines the `MaxLines` property.
1281        pub fn max_lines_property() -> StyledProperty<i32> {
1282            FerroProperty::register::<TextBox, _>("MaxLines", 0)
1283        }
1284    );
1285
1286    ferro_property!(
1287        /// Defines the `MinLines` property.
1288        pub fn min_lines_property() -> StyledProperty<i32> {
1289            FerroProperty::register::<TextBox, _>("MinLines", 0)
1290        }
1291    );
1292
1293    ferro_property!(
1294        /// Defines the `Text` property.
1295        pub fn text_property() -> StyledProperty<Option<String>> {
1296            TextBlock::text_property().add_owner_with::<TextBox>(
1297                StyledPropertyMetadata::new(None)
1298                    .with_coerce(Self::coerce_text_callback)
1299                    .with_default_binding_mode(BindingMode::TwoWay)
1300                    .with_enable_data_validation(true),
1301            )
1302        }
1303    );
1304
1305    ferro_property!(
1306        /// Defines the `TextAlignment` property.
1307        pub fn text_alignment_property() -> AttachedProperty<TextAlignment> {
1308            TextBlock::text_alignment_property().add_owner::<TextBox>()
1309        }
1310    );
1311
1312    ferro_property!(
1313        /// Defines the `HorizontalContentAlignment` property.
1314        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
1315            ContentControl::horizontal_content_alignment_property().add_owner::<TextBox>()
1316        }
1317    );
1318
1319    ferro_property!(
1320        /// Defines the `VerticalContentAlignment` property.
1321        pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
1322            ContentControl::vertical_content_alignment_property().add_owner::<TextBox>()
1323        }
1324    );
1325
1326    ferro_property!(
1327        /// Defines the `TextWrapping` property.
1328        pub fn text_wrapping_property() -> AttachedProperty<TextWrapping> {
1329            TextBlock::text_wrapping_property().add_owner::<TextBox>()
1330        }
1331    );
1332
1333    ferro_property!(
1334        /// Defines the `LineHeight` property.
1335        pub fn line_height_property() -> AttachedProperty<f64> {
1336            TextBlock::line_height_property().add_owner_with::<TextBox>(StyledPropertyMetadata::new(Some(f64::NAN)))
1337        }
1338    );
1339
1340    ferro_property!(
1341        /// Defines the `PlaceholderText` property.
1342        pub fn placeholder_text_property() -> StyledProperty<Option<String>> {
1343            FerroProperty::register::<TextBox, _>("PlaceholderText", None)
1344        }
1345    );
1346} }
1347
1348impl TextBox {
1349    /// Defines the `Watermark` property.
1350    #[deprecated(note = "Use placeholder_text_property instead.")]
1351    pub fn watermark_property() -> &'static StyledProperty<Option<String>> {
1352        Self::placeholder_text_property()
1353    }
1354
1355    ferro_property!(for TextBox;
1356        /// Defines the `UseFloatingPlaceholder` property.
1357        pub fn use_floating_placeholder_property() -> StyledProperty<bool> {
1358            FerroProperty::register::<TextBox, _>("UseFloatingPlaceholder", false)
1359        }
1360    );
1361
1362    /// Defines the `UseFloatingWatermark` property.
1363    #[deprecated(note = "Use use_floating_placeholder_property instead.")]
1364    pub fn use_floating_watermark_property() -> &'static StyledProperty<bool> {
1365        Self::use_floating_placeholder_property()
1366    }
1367
1368    ferro_property!(for TextBox;
1369        /// Defines the `PlaceholderForeground` property.
1370        pub fn placeholder_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
1371            FerroProperty::register::<TextBox, _>("PlaceholderForeground", None)
1372        }
1373    );
1374
1375    /// Defines the `WatermarkForeground` property.
1376    #[deprecated(note = "Use placeholder_foreground_property instead.")]
1377    pub fn watermark_foreground_property() -> &'static StyledProperty<Option<Rc<dyn IBrush>>> {
1378        Self::placeholder_foreground_property()
1379    }
1380
1381    ferro_property!(for TextBox;
1382        /// Defines the `NewLine` property.
1383        pub fn new_line_property() -> StyledProperty<String> {
1384            FerroProperty::register::<TextBox, _>("NewLine", environment_new_line().to_owned())
1385        }
1386    );
1387
1388    ferro_property!(for TextBox;
1389        /// Defines the `InnerLeftContent` property.
1390        pub fn inner_left_content_property() -> StyledProperty<Option<BoxedValue>> {
1391            FerroProperty::register::<TextBox, _>("InnerLeftContent", None)
1392        }
1393    );
1394
1395    ferro_property!(for TextBox;
1396        /// Defines the `InnerRightContent` property.
1397        pub fn inner_right_content_property() -> StyledProperty<Option<BoxedValue>> {
1398            FerroProperty::register::<TextBox, _>("InnerRightContent", None)
1399        }
1400    );
1401
1402    ferro_property!(for TextBox;
1403        /// Defines the `RevealPassword` property.
1404        pub fn reveal_password_property() -> StyledProperty<bool> {
1405            FerroProperty::register::<TextBox, _>("RevealPassword", false)
1406        }
1407    );
1408
1409    ferro_property!(for TextBox;
1410        /// Defines the `CanCut` property.
1411        pub fn can_cut_property() -> DirectProperty<TextBox, bool> {
1412            FerroProperty::register_direct::<TextBox, _>("CanCut", |o| o.can_cut(), None, false)
1413        }
1414    );
1415
1416    ferro_property!(for TextBox;
1417        /// Defines the `CanCopy` property.
1418        pub fn can_copy_property() -> DirectProperty<TextBox, bool> {
1419            FerroProperty::register_direct::<TextBox, _>("CanCopy", |o| o.can_copy(), None, false)
1420        }
1421    );
1422
1423    ferro_property!(for TextBox;
1424        /// Defines the `CanPaste` property.
1425        pub fn can_paste_property() -> DirectProperty<TextBox, bool> {
1426            FerroProperty::register_direct::<TextBox, _>("CanPaste", |o| o.can_paste(), None, false)
1427        }
1428    );
1429
1430    ferro_property!(for TextBox;
1431        /// Defines the `IsUndoEnabled` property.
1432        pub fn is_undo_enabled_property() -> StyledProperty<bool> {
1433            FerroProperty::register::<TextBox, _>("IsUndoEnabled", true)
1434        }
1435    );
1436
1437    ferro_property!(for TextBox;
1438        /// Defines the `UndoLimit` property.
1439        pub fn undo_limit_property() -> StyledProperty<i32> {
1440            FerroProperty::register::<TextBox, _>("UndoLimit", UndoRedoHelper::<UndoRedoState>::DEFAULT_UNDO_LIMIT)
1441        }
1442    );
1443
1444    ferro_property!(for TextBox;
1445        /// Defines the `CanUndo` property.
1446        pub fn can_undo_property() -> DirectProperty<TextBox, bool> {
1447            FerroProperty::register_direct::<TextBox, _>("CanUndo", |o| o.can_undo(), None, false)
1448        }
1449    );
1450
1451    ferro_property!(for TextBox;
1452        /// Defines the `CanRedo` property.
1453        pub fn can_redo_property() -> DirectProperty<TextBox, bool> {
1454            FerroProperty::register_direct::<TextBox, _>("CanRedo", |o| o.can_redo(), None, false)
1455        }
1456    );
1457
1458    ferro_routed_event!(
1459        /// Defines the `CopyingToClipboard` event.
1460        pub fn copying_to_clipboard_event() -> RoutedEvent<RoutedEventArgs> {
1461            RoutedEvent::register::<TextBox, _>("CopyingToClipboard", RoutingStrategies::BUBBLE)
1462        }
1463    );
1464
1465    ferro_routed_event!(
1466        /// Defines the `CuttingToClipboard` event.
1467        pub fn cutting_to_clipboard_event() -> RoutedEvent<RoutedEventArgs> {
1468            RoutedEvent::register::<TextBox, _>("CuttingToClipboard", RoutingStrategies::BUBBLE)
1469        }
1470    );
1471
1472    ferro_routed_event!(
1473        /// Defines the `PastingFromClipboard` event.
1474        ///
1475        /// The event is raised with [`PastingFromClipboardEventArgs`].
1476        pub fn pasting_from_clipboard_event() -> RoutedEvent<RoutedEventArgs> {
1477            RoutedEvent::register::<TextBox, _>("PastingFromClipboard", RoutingStrategies::BUBBLE)
1478        }
1479    );
1480
1481    ferro_routed_event!(
1482        /// Defines the `TextChanged` event.
1483        pub fn text_changed_event() -> RoutedEvent<TextChangedEventArgs> {
1484            RoutedEvent::register::<TextBox, _>("TextChanged", RoutingStrategies::BUBBLE)
1485        }
1486    );
1487
1488    ferro_routed_event!(
1489        /// Defines the `TextChanging` event.
1490        pub fn text_changing_event() -> RoutedEvent<TextChangingEventArgs> {
1491            RoutedEvent::register::<TextBox, _>("TextChanging", RoutingStrategies::BUBBLE)
1492        }
1493    );
1494
1495    fn static_constructor() {
1496        InputElement::focusable_property().override_default_value::<TextBox>(true);
1497        // Not ported: the `Auto` default of `PlatformFeedback.FeedbackType` (waits for `PlatformFeedback`).
1498        InputElement::text_input_method_client_requested_event().add_class_handler::<TextBox>(|tb, e| {
1499            if !tb.is_read_only() {
1500                e.set_client(Some(tb.im_client.clone()));
1501            }
1502        });
1503    }
1504
1505    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
1506    pub fn construct() -> Self {
1507        Self {
1508            base: TemplatedControl::construct(),
1509            touch_radius: OnceCell::new(),
1510            presenter: RefCell::new(None),
1511            scroll_viewer: RefCell::new(None),
1512            im_client: TextBoxTextInputMethodClient::new(),
1513            undo_redo_helper: UndoRedoHelper::unattached(),
1514            undo_redo_host: RefCell::new(None),
1515            is_undoing_redoing: Cell::new(false),
1516            text_mutation_kind: Cell::new(TextMutationKind::ExternalReplacement),
1517            needs_undo_redo_snapshot_after_text_change: Cell::new(false),
1518            can_cut: Cell::new(false),
1519            can_copy: Cell::new(false),
1520            can_paste: Cell::new(false),
1521            can_undo: Cell::new(false),
1522            can_redo: Cell::new(false),
1523            word_selection_start: Cell::new(-1),
1524            selection_at_pointer_press: Cell::new((0, 0)),
1525            selected_text_changes_made_since_last_undo_snapshot: Cell::new(0),
1526            has_done_snapshot_once: Cell::new(false),
1527            current_click_count: Cell::new(0),
1528            is_double_tapped: Cell::new(false),
1529            is_in_touch_mode: Cell::new(false),
1530            last_point: Cell::new(Point::default()),
1531            is_in_touch_selection_mode: Cell::new(false),
1532            is_in_touch_caret_mode: Cell::new(false),
1533            has_touch_selection: Cell::new(false),
1534            presenter_property_changed: RefCell::new(None),
1535        }
1536    }
1537
1538    /// Initializes a new instance of the `TextBox` class.
1539    pub fn new() -> Ref<Self> {
1540        instantiate(Self::construct())
1541    }
1542
1543    /// Whether the text box shows a selection highlight when it is not
1544    /// focused.
1545    pub fn is_inactive_selection_highlight_enabled(&self) -> bool {
1546        self.get_value(Self::is_inactive_selection_highlight_enabled_property())
1547    }
1548
1549    pub fn set_is_inactive_selection_highlight_enabled(&self, value: bool) {
1550        self.set_value(Self::is_inactive_selection_highlight_enabled_property(), value)
1551    }
1552
1553    /// Whether the text box clears its selection after it loses focus.
1554    pub fn clear_selection_on_lost_focus(&self) -> bool {
1555        self.get_value(Self::clear_selection_on_lost_focus_property())
1556    }
1557
1558    pub fn set_clear_selection_on_lost_focus(&self, value: bool) {
1559        self.set_value(Self::clear_selection_on_lost_focus_property(), value)
1560    }
1561
1562    /// Whether the text box allows and displays newline or return
1563    /// characters.
1564    pub fn accepts_return(&self) -> bool {
1565        self.get_value(Self::accepts_return_property())
1566    }
1567
1568    pub fn set_accepts_return(&self, value: bool) {
1569        self.set_value(Self::accepts_return_property(), value)
1570    }
1571
1572    /// Whether the text box allows and displays tabs.
1573    pub fn accepts_tab(&self) -> bool {
1574        self.get_value(Self::accepts_tab_property())
1575    }
1576
1577    pub fn set_accepts_tab(&self, value: bool) {
1578        self.set_value(Self::accepts_tab_property(), value)
1579    }
1580
1581    /// The index of the text caret.
1582    pub fn caret_index(&self) -> i32 {
1583        self.get_value(Self::caret_index_property())
1584    }
1585
1586    pub fn set_caret_index(&self, value: i32) {
1587        self.set_value(Self::caret_index_property(), value)
1588    }
1589
1590    fn on_caret_index_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1591        if self.is_undo_enabled() {
1592            if let Some(state) = self.undo_redo_helper.try_get_last_state() {
1593                if state.text == self.text() {
1594                    self.undo_redo_helper.update_last_state();
1595                }
1596            }
1597        }
1598
1599        let _change = self.im_client.begin_change();
1600
1601        let new_value = e.get_new_value::<i32>();
1602        self.set_current_value(Self::selection_start_property(), new_value);
1603        self.set_current_value(Self::selection_end_property(), new_value);
1604
1605        if let Some(presenter) = self.presenter() {
1606            presenter.set_current_value(TextPresenter::caret_index_property(), new_value);
1607        }
1608    }
1609
1610    /// Whether this text box is read-only.
1611    pub fn is_read_only(&self) -> bool {
1612        self.get_value(Self::is_read_only_property())
1613    }
1614
1615    pub fn set_is_read_only(&self, value: bool) {
1616        self.set_value(Self::is_read_only_property(), value)
1617    }
1618
1619    /// The character that should be used for password masking (`'\0'`:
1620    /// none).
1621    pub fn password_char(&self) -> char {
1622        self.get_value(Self::password_char_property())
1623    }
1624
1625    pub fn set_password_char(&self, value: char) {
1626        self.set_value(Self::password_char_property(), value)
1627    }
1628
1629    /// A brush that is used to highlight selected text.
1630    pub fn selection_brush(&self) -> Option<Rc<dyn IBrush>> {
1631        self.get_value(Self::selection_brush_property())
1632    }
1633
1634    pub fn set_selection_brush(&self, value: Option<Rc<dyn IBrush>>) {
1635        self.set_value(Self::selection_brush_property(), value)
1636    }
1637
1638    /// A brush that is used for the foreground of selected text.
1639    pub fn selection_foreground_brush(&self) -> Option<Rc<dyn IBrush>> {
1640        self.get_value(Self::selection_foreground_brush_property())
1641    }
1642
1643    pub fn set_selection_foreground_brush(&self, value: Option<Rc<dyn IBrush>>) {
1644        self.set_value(Self::selection_foreground_brush_property(), value)
1645    }
1646
1647    /// A brush that is used for the text caret.
1648    pub fn caret_brush(&self) -> Option<Rc<dyn IBrush>> {
1649        self.get_value(Self::caret_brush_property())
1650    }
1651
1652    pub fn set_caret_brush(&self, value: Option<Rc<dyn IBrush>>) {
1653        self.set_value(Self::caret_brush_property(), value)
1654    }
1655
1656    /// The caret blink rate.
1657    pub fn caret_blink_interval(&self) -> TimeSpan {
1658        self.get_value(Self::caret_blink_interval_property())
1659    }
1660
1661    pub fn set_caret_blink_interval(&self, value: TimeSpan) {
1662        self.set_value(Self::caret_blink_interval_property(), value)
1663    }
1664
1665    /// The starting position of the text selected in the text box.
1666    pub fn selection_start(&self) -> i32 {
1667        self.get_value(Self::selection_start_property())
1668    }
1669
1670    pub fn set_selection_start(&self, value: i32) {
1671        self.set_value(Self::selection_start_property(), value)
1672    }
1673
1674    fn on_selection_start_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1675        self.update_command_states();
1676
1677        let value = e.get_new_value::<i32>();
1678        if self.selection_end() == value && self.caret_index() != value {
1679            self.set_current_value(Self::caret_index_property(), value);
1680        }
1681    }
1682
1683    /// The end position of the text selected in the text box.
1684    ///
1685    /// When the `SelectionEnd` is equal to `SelectionStart`, there is no
1686    /// selected text and it marks the caret position.
1687    pub fn selection_end(&self) -> i32 {
1688        self.get_value(Self::selection_end_property())
1689    }
1690
1691    pub fn set_selection_end(&self, value: i32) {
1692        self.set_value(Self::selection_end_property(), value)
1693    }
1694
1695    fn on_selection_end_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
1696        self.update_command_states();
1697
1698        let value = e.get_new_value::<i32>();
1699        if self.selection_start() == value && self.caret_index() != value {
1700            self.set_current_value(Self::caret_index_property(), value);
1701        }
1702    }
1703
1704    /// The maximum number of characters that the text box can accept. This
1705    /// constraint only applies for manually entered (user-inputted) text.
1706    pub fn max_length(&self) -> i32 {
1707        self.get_value(Self::max_length_property())
1708    }
1709
1710    pub fn set_max_length(&self, value: i32) {
1711        self.set_value(Self::max_length_property(), value)
1712    }
1713
1714    /// The maximum number of visible lines to size to.
1715    pub fn max_lines(&self) -> i32 {
1716        self.get_value(Self::max_lines_property())
1717    }
1718
1719    pub fn set_max_lines(&self, value: i32) {
1720        self.set_value(Self::max_lines_property(), value)
1721    }
1722
1723    /// The minimum number of visible lines to size to.
1724    pub fn min_lines(&self) -> i32 {
1725        self.get_value(Self::min_lines_property())
1726    }
1727
1728    pub fn set_min_lines(&self, value: i32) {
1729        self.set_value(Self::min_lines_property(), value)
1730    }
1731
1732    /// The line height.
1733    pub fn line_height(&self) -> f64 {
1734        self.get_value(Self::line_height_property())
1735    }
1736
1737    pub fn set_line_height(&self, value: f64) {
1738        self.set_value(Self::line_height_property(), value)
1739    }
1740
1741    /// The text content of the text box.
1742    pub fn text(&self) -> Option<String> {
1743        self.get_value(Self::text_property())
1744    }
1745
1746    pub fn set_text(&self, value: Option<&str>) {
1747        self.set_value(Self::text_property(), value.map(str::to_owned))
1748    }
1749
1750    fn coerce_text_callback(sender: &FerroObject, value: Option<String>) -> Option<String> {
1751        match sender.downcast_ref::<TextBox>() {
1752            Some(text_box) => text_box.coerce_text(value),
1753            None => value,
1754        }
1755    }
1756
1757    /// The text selected in the text box.
1758    pub fn selected_text(&self) -> String {
1759        self.get_selection()
1760    }
1761
1762    /// Replaces the selected text; a null or empty value deletes it.
1763    pub fn set_selected_text(&self, value: Option<&str>) {
1764        match value {
1765            Some(value) if !value.is_empty() => self.handle_text_input(Some(value)),
1766            _ => {
1767                self.selected_text_changes_made_since_last_undo_snapshot
1768                    .set(self.selected_text_changes_made_since_last_undo_snapshot.get() + 1);
1769                self.snapshot_undo_redo(false);
1770                self.delete_selection();
1771            }
1772        }
1773    }
1774
1775    /// The horizontal alignment of the content within the control.
1776    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
1777        self.get_value(Self::horizontal_content_alignment_property())
1778    }
1779
1780    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
1781        self.set_value(Self::horizontal_content_alignment_property(), value)
1782    }
1783
1784    /// The vertical alignment of the content within the control.
1785    pub fn vertical_content_alignment(&self) -> VerticalAlignment {
1786        self.get_value(Self::vertical_content_alignment_property())
1787    }
1788
1789    pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
1790        self.set_value(Self::vertical_content_alignment_property(), value)
1791    }
1792
1793    /// The text alignment of the text box.
1794    pub fn text_alignment(&self) -> TextAlignment {
1795        self.get_value(Self::text_alignment_property())
1796    }
1797
1798    pub fn set_text_alignment(&self, value: TextAlignment) {
1799        self.set_value(Self::text_alignment_property(), value)
1800    }
1801
1802    /// The placeholder or descriptive text that is displayed even if the
1803    /// `Text` property is not yet set.
1804    pub fn placeholder_text(&self) -> Option<String> {
1805        self.get_value(Self::placeholder_text_property())
1806    }
1807
1808    pub fn set_placeholder_text(&self, value: Option<&str>) {
1809        self.set_value(Self::placeholder_text_property(), value.map(str::to_owned))
1810    }
1811
1812    /// The placeholder or descriptive text that is displayed even if the
1813    /// `Text` property is not yet set.
1814    #[deprecated(note = "Use placeholder_text instead.")]
1815    pub fn watermark(&self) -> Option<String> {
1816        self.placeholder_text()
1817    }
1818
1819    #[deprecated(note = "Use set_placeholder_text instead.")]
1820    pub fn set_watermark(&self, value: Option<&str>) {
1821        self.set_placeholder_text(value)
1822    }
1823
1824    /// Whether the `PlaceholderText` will still be shown above the `Text`
1825    /// even after a text value is set.
1826    pub fn use_floating_placeholder(&self) -> bool {
1827        self.get_value(Self::use_floating_placeholder_property())
1828    }
1829
1830    pub fn set_use_floating_placeholder(&self, value: bool) {
1831        self.set_value(Self::use_floating_placeholder_property(), value)
1832    }
1833
1834    /// Whether the `PlaceholderText` will still be shown above the `Text`
1835    /// even after a text value is set.
1836    #[deprecated(note = "Use use_floating_placeholder instead.")]
1837    pub fn use_floating_watermark(&self) -> bool {
1838        self.use_floating_placeholder()
1839    }
1840
1841    #[deprecated(note = "Use set_use_floating_placeholder instead.")]
1842    pub fn set_use_floating_watermark(&self, value: bool) {
1843        self.set_use_floating_placeholder(value)
1844    }
1845
1846    /// The brush used for the foreground color of the placeholder text.
1847    pub fn placeholder_foreground(&self) -> Option<Rc<dyn IBrush>> {
1848        self.get_value(Self::placeholder_foreground_property())
1849    }
1850
1851    pub fn set_placeholder_foreground(&self, value: Option<Rc<dyn IBrush>>) {
1852        self.set_value(Self::placeholder_foreground_property(), value)
1853    }
1854
1855    /// The brush used for the foreground color of the placeholder text.
1856    #[deprecated(note = "Use placeholder_foreground instead.")]
1857    pub fn watermark_foreground(&self) -> Option<Rc<dyn IBrush>> {
1858        self.placeholder_foreground()
1859    }
1860
1861    #[deprecated(note = "Use set_placeholder_foreground instead.")]
1862    pub fn set_watermark_foreground(&self, value: Option<Rc<dyn IBrush>>) {
1863        self.set_placeholder_foreground(value)
1864    }
1865
1866    /// Custom content that is positioned on the left side of the text
1867    /// layout box.
1868    pub fn inner_left_content(&self) -> Option<BoxedValue> {
1869        self.get_value(Self::inner_left_content_property())
1870    }
1871
1872    pub fn set_inner_left_content(&self, value: Option<BoxedValue>) {
1873        self.set_value(Self::inner_left_content_property(), value)
1874    }
1875
1876    /// Custom content that is positioned on the right side of the text
1877    /// layout box.
1878    pub fn inner_right_content(&self) -> Option<BoxedValue> {
1879        self.get_value(Self::inner_right_content_property())
1880    }
1881
1882    pub fn set_inner_right_content(&self, value: Option<BoxedValue>) {
1883        self.set_value(Self::inner_right_content_property(), value)
1884    }
1885
1886    /// Whether text masked by `PasswordChar` should be revealed.
1887    pub fn reveal_password(&self) -> bool {
1888        self.get_value(Self::reveal_password_property())
1889    }
1890
1891    pub fn set_reveal_password(&self, value: bool) {
1892        self.set_value(Self::reveal_password_property(), value)
1893    }
1894
1895    /// The text wrapping of the text box.
1896    pub fn text_wrapping(&self) -> TextWrapping {
1897        self.get_value(Self::text_wrapping_property())
1898    }
1899
1900    pub fn set_text_wrapping(&self, value: TextWrapping) {
1901        self.set_value(Self::text_wrapping_property(), value)
1902    }
1903
1904    /// Which characters are inserted when Enter is pressed. Default: the
1905    /// line break of the platform.
1906    pub fn new_line(&self) -> String {
1907        self.get_value(Self::new_line_property())
1908    }
1909
1910    pub fn set_new_line(&self, value: &str) {
1911        self.set_value(Self::new_line_property(), value.to_owned())
1912    }
1913
1914    /// Clears the current selection, maintaining the `CaretIndex`.
1915    pub fn clear_selection(&self) {
1916        self.set_current_value(Self::caret_index_property(), self.selection_start());
1917        self.set_current_value(Self::selection_end_property(), self.selection_start());
1918    }
1919
1920    /// Whether the cut command can be executed.
1921    pub fn can_cut(&self) -> bool {
1922        self.can_cut.get()
1923    }
1924
1925    fn set_can_cut(&self, value: bool) {
1926        self.set_and_raise_cell(Self::can_cut_property(), &self.can_cut, value);
1927    }
1928
1929    /// Whether the copy command can be executed.
1930    pub fn can_copy(&self) -> bool {
1931        self.can_copy.get()
1932    }
1933
1934    fn set_can_copy(&self, value: bool) {
1935        self.set_and_raise_cell(Self::can_copy_property(), &self.can_copy, value);
1936    }
1937
1938    /// Whether the paste command can be executed.
1939    pub fn can_paste(&self) -> bool {
1940        self.can_paste.get()
1941    }
1942
1943    fn set_can_paste(&self, value: bool) {
1944        self.set_and_raise_cell(Self::can_paste_property(), &self.can_paste, value);
1945    }
1946
1947    /// Whether undo/redo is enabled.
1948    pub fn is_undo_enabled(&self) -> bool {
1949        self.get_value(Self::is_undo_enabled_property())
1950    }
1951
1952    pub fn set_is_undo_enabled(&self, value: bool) {
1953        self.set_value(Self::is_undo_enabled_property(), value)
1954    }
1955
1956    /// The maximum number of items that can reside in the undo stack.
1957    pub fn undo_limit(&self) -> i32 {
1958        self.get_value(Self::undo_limit_property())
1959    }
1960
1961    pub fn set_undo_limit(&self, value: i32) {
1962        self.set_value(Self::undo_limit_property(), value)
1963    }
1964
1965    fn on_undo_limit_changed(&self, new_value: i32) {
1966        self.undo_redo_helper.set_limit(new_value);
1967
1968        // "Setting UndoLimit clears the undo queue."
1969        self.clear_undo_redo();
1970    }
1971
1972    /// Whether the undo stack has an action that can be undone.
1973    pub fn can_undo(&self) -> bool {
1974        self.can_undo.get()
1975    }
1976
1977    fn set_can_undo(&self, value: bool) {
1978        self.set_and_raise_cell(Self::can_undo_property(), &self.can_undo, value);
1979    }
1980
1981    /// Whether the redo stack has an action that can be redone.
1982    pub fn can_redo(&self) -> bool {
1983        self.can_redo.get()
1984    }
1985
1986    fn set_can_redo(&self, value: bool) {
1987        self.set_and_raise_cell(Self::can_redo_property(), &self.can_redo, value);
1988    }
1989
1990    /// The number of lines in the text box, or -1 if no layout information
1991    /// is available.
1992    ///
1993    /// If the text wraps, changing the width of the text box may change this
1994    /// value. The value returned is the number of lines in the entire text
1995    /// box, regardless of how many are currently in view.
1996    pub fn get_line_count(&self) -> i32 {
1997        match self.presenter() {
1998            Some(presenter) => presenter.text_layout().text_lines().len() as i32,
1999            None => -1,
2000        }
2001    }
2002
2003    /// Raised when content is being copied to the clipboard.
2004    pub fn copying_to_clipboard(
2005        &self,
2006        handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
2007    ) -> RoutedEventHandlerToken {
2008        self.add_handler(Self::copying_to_clipboard_event(), handler)
2009    }
2010
2011    /// Raised when content is being cut to the clipboard.
2012    pub fn cutting_to_clipboard(
2013        &self,
2014        handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
2015    ) -> RoutedEventHandlerToken {
2016        self.add_handler(Self::cutting_to_clipboard_event(), handler)
2017    }
2018
2019    /// Raised when content is being pasted from the clipboard.
2020    ///
2021    /// The event is raised with [`PastingFromClipboardEventArgs`]: the
2022    /// handler receives the args as raised, so that it can reach them with
2023    /// `e.downcast_ref::<PastingFromClipboardEventArgs>()` (the event itself
2024    /// is declared with the base args, as in the reference).
2025    pub fn pasting_from_clipboard(
2026        &self,
2027        handler: impl Fn(&Interactive, &dyn IRoutedEventArgs) + 'static,
2028    ) -> RoutedEventHandlerToken {
2029        self.add_route_handler(
2030            Self::pasting_from_clipboard_event(),
2031            Rc::new(handler),
2032            Interactive::DEFAULT_ROUTES,
2033            false,
2034        )
2035    }
2036
2037    /// Occurs asynchronously after text changes and the new text is
2038    /// rendered.
2039    pub fn text_changed(
2040        &self,
2041        handler: impl Fn(&Interactive, &TextChangedEventArgs) + 'static,
2042    ) -> RoutedEventHandlerToken {
2043        self.add_handler(Self::text_changed_event(), handler)
2044    }
2045
2046    /// Occurs synchronously when text starts to change but before it is
2047    /// rendered.
2048    ///
2049    /// This event occurs just after the `Text` property value has been
2050    /// updated.
2051    pub fn text_changing(
2052        &self,
2053        handler: impl Fn(&Interactive, &TextChangingEventArgs) + 'static,
2054    ) -> RoutedEventHandlerToken {
2055        self.add_handler(Self::text_changing_event(), handler)
2056    }
2057
2058    fn presenter(&self) -> Option<Ref<TextPresenter>> {
2059        self.presenter.borrow().clone()
2060    }
2061
2062    fn scroll_viewer(&self) -> Option<Ref<ScrollViewer>> {
2063        self.scroll_viewer.borrow().clone()
2064    }
2065
2066    /// The UTF-16 form of the text (empty when the text is null).
2067    fn text_utf16(&self) -> Vec<u16> {
2068        self.text().map(|text| text.encode_utf16().collect()).unwrap_or_default()
2069    }
2070
2071    /// The length of the text in UTF-16 code units (0 when the text is
2072    /// null).
2073    fn text_length(&self) -> i32 {
2074        self.text().map_or(0, |text| utf16_length(&text))
2075    }
2076
2077    fn touch_radius(&self) -> i32 {
2078        *self.touch_radius.get_or_init(|| {
2079            let height = FerroLocator::current()
2080                .get_service::<dyn IPlatformSettings>()
2081                .map_or(10.0, |settings| settings.get_tap_size(PointerType::Touch).height);
2082
2083            (height / 2.0) as i32 + 5
2084        })
2085    }
2086
2087    fn presenter_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
2088        if e.property() == TextPresenter::preedit_text_property().as_property() {
2089            let (old_value, new_value) = e.get_old_and_new_value::<Option<String>>();
2090
2091            if old_value.is_none_or(|old_value| old_value.is_empty())
2092                && new_value.is_some_and(|new_value| !new_value.is_empty())
2093            {
2094                self.pseudo_classes().set(":empty", false);
2095
2096                self.delete_selection();
2097            }
2098        }
2099    }
2100
2101    fn update_command_states(&self) {
2102        let has_selection = self.has_selection();
2103        self.set_can_copy(!self.is_password_box() && has_selection);
2104        self.set_can_cut(!self.is_password_box() && has_selection && !self.is_read_only());
2105        self.set_can_paste(!self.is_read_only());
2106    }
2107
2108    /// Inserts typed or pasted text at the caret, replacing the selection.
2109    pub(crate) fn handle_text_input(&self, input: Option<&str>) {
2110        if self.is_read_only() {
2111            return;
2112        }
2113
2114        let Some(mut input) = self.sanitize_input_text(input).filter(|input| !input.is_empty()) else {
2115            return;
2116        };
2117
2118        self.selected_text_changes_made_since_last_undo_snapshot
2119            .set(self.selected_text_changes_made_since_last_undo_snapshot.get() + 1);
2120        self.snapshot_undo_redo(false);
2121
2122        let mut text = self.text_utf16();
2123        let selection_length = (self.selection_start() - self.selection_end()).abs();
2124        let new_length = input.len() as i32 + text.len() as i32 - selection_length;
2125
2126        let max_length = self.max_length();
2127
2128        if max_length > 0 && new_length > max_length {
2129            let length = (input.len() as i32 - (new_length - max_length)).max(0);
2130            input.truncate(length as usize);
2131        }
2132
2133        if !input.is_empty() {
2134            let mut caret_index = self.caret_index();
2135
2136            if selection_length != 0 {
2137                let (start, _) = self.get_selection_range();
2138
2139                remove_range(&mut text, start, selection_length);
2140
2141                caret_index = start;
2142            }
2143
2144            let insert_at = (caret_index.max(0) as usize).min(text.len());
2145            let input_length = input.len() as i32;
2146
2147            text.splice(insert_at..insert_at, input);
2148
2149            let text = String::from_utf16_lossy(&text);
2150
2151            self.set_text_from_edit(Some(text.clone()));
2152
2153            self.clear_selection();
2154
2155            if self.is_undo_enabled() {
2156                self.undo_redo_helper.discard_redo();
2157            }
2158
2159            let presenter = self.presenter();
2160
2161            // Make sure the updated text is in sync.
2162            if let Some(presenter) = &presenter {
2163                presenter.set_current_value(TextPresenter::text_property(), Some(text));
2164            }
2165
2166            caret_index += input_length;
2167
2168            // Make sure the caret is in sync.
2169            if let Some(presenter) = &presenter {
2170                presenter.move_caret_to_text_position(caret_index, false);
2171            }
2172
2173            self.set_current_value(Self::caret_index_property(), caret_index);
2174        }
2175    }
2176
2177    /// The UTF-16 form of an input without the characters the text box does
2178    /// not accept: everything from the first line break on when the text box
2179    /// does not accept returns, and the invalid characters.
2180    fn sanitize_input_text(&self, text: Option<&str>) -> Option<Vec<u16>> {
2181        let mut text: Vec<u16> = text?.encode_utf16().collect();
2182
2183        if !self.accepts_return() {
2184            let mut line_break_start = 0;
2185            let mut grapheme_enumerator = GraphemeEnumerator::new(&text);
2186
2187            while let Some(grapheme) = grapheme_enumerator.move_next() {
2188                if grapheme.first_codepoint().is_break_char() {
2189                    break;
2190                }
2191
2192                line_break_start += grapheme.length();
2193            }
2194
2195            // All lines except the first one are discarded when the text box does not accept the Return key.
2196            text.truncate(line_break_start);
2197        }
2198
2199        text.retain(|unit| !INVALID_CHARACTERS.contains(unit));
2200
2201        Some(text)
2202    }
2203
2204    /// Cuts the current text onto the clipboard.
2205    ///
2206    /// Like the asynchronous method of the reference, everything up to the
2207    /// clipboard call runs before this returns; the selection is deleted
2208    /// once the clipboard has taken the text, from a dispatcher job unless
2209    /// the clipboard completed at once. A failure platform clipboards are
2210    /// known for (see `ClipboardHelper::is_expected_clipboard_exception`)
2211    /// is logged and leaves the selection in place; any other failure, and
2212    /// a panic of the clipboard, is raised on the dispatcher.
2213    pub fn cut(&self) {
2214        let text = self.get_selection();
2215
2216        if text.is_empty() {
2217            return;
2218        }
2219
2220        let event_args = RoutedEventArgs::with_event(Self::cutting_to_clipboard_event());
2221        self.raise_event(&event_args);
2222        if !event_args.handled() {
2223            self.snapshot_undo_redo(true);
2224
2225            let Some(clipboard) = TopLevel::get_top_level(Some(self)).and_then(|top_level| top_level.clipboard())
2226            else {
2227                return;
2228            };
2229
2230            let this = self.to_ref();
2231
2232            ClipboardHelper::start(async move {
2233                match clipboard.set_text_async(Some(&text)).await {
2234                    Ok(()) => {}
2235                    Err(ex) if ClipboardHelper::is_expected_clipboard_exception(&ex) => {
2236                        if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::CONTROL) {
2237                            let source: &dyn Any = &this;
2238                            logger.log_with_values(Some(source), "Failed to write text to clipboard: {Error}", &[&ex]);
2239                        }
2240                        return Ok(());
2241                    }
2242                    Err(ex) => return Err(ex),
2243                }
2244
2245                this.delete_selection();
2246                Ok(())
2247            });
2248        }
2249    }
2250
2251    /// Copies the current text onto the clipboard.
2252    ///
2253    /// See [`cut`](Self::cut) for how the clipboard operation runs.
2254    pub fn copy(&self) {
2255        let text = self.get_selection();
2256
2257        if text.is_empty() {
2258            return;
2259        }
2260
2261        let event_args = RoutedEventArgs::with_event(Self::copying_to_clipboard_event());
2262        self.raise_event(&event_args);
2263        if !event_args.handled() {
2264            let Some(clipboard) = TopLevel::get_top_level(Some(self)).and_then(|top_level| top_level.clipboard())
2265            else {
2266                return;
2267            };
2268
2269            let this = self.to_ref();
2270
2271            ClipboardHelper::start(async move {
2272                match clipboard.set_text_async(Some(&text)).await {
2273                    Ok(()) => {}
2274                    Err(ex) if ClipboardHelper::is_expected_clipboard_exception(&ex) => {
2275                        if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::CONTROL) {
2276                            let source: &dyn Any = &this;
2277                            logger.log_with_values(Some(source), "Failed to write text to clipboard: {Error}", &[&ex]);
2278                        }
2279                    }
2280                    Err(ex) => return Err(ex),
2281                }
2282
2283                Ok(())
2284            });
2285        }
2286    }
2287
2288    /// Pastes the current clipboard text content into the text box.
2289    ///
2290    /// See [`cut`](Self::cut) for how the clipboard operation runs.
2291    pub fn paste(&self) {
2292        let pasted = self.paste_core_async(TopLevel::get_top_level(Some(self)).and_then(|top_level| top_level.clipboard()));
2293
2294        ClipboardHelper::start(async move {
2295            match pasted.await {
2296                Ok(result) => result,
2297                // A paste that was dropped with the dispatcher has nothing left to do.
2298                Err(_) => Ok(()),
2299            }
2300        });
2301    }
2302
2303    /// Pastes the text of a clipboard into the text box, once the clipboard
2304    /// has delivered it. The pasting event is raised before this returns.
2305    ///
2306    /// A failure platform clipboards are known for is logged and pastes
2307    /// nothing; any other failure is the result of the task, for whoever
2308    /// awaits it.
2309    fn paste_core_async(&self, clipboard: Option<Rc<dyn IClipboard>>) -> DispatcherTask<Result<(), ClipboardError>> {
2310        let this = self.to_ref();
2311
2312        Dispatcher::ui_thread().to_task_scheduler().start_local(async move {
2313            let event_args =
2314                PastingFromClipboardEventArgs::new(Self::pasting_from_clipboard_event(), clipboard.clone());
2315            this.raise_event(&event_args);
2316
2317            let Some(clipboard) = clipboard.filter(|_| !event_args.handled()) else {
2318                return Ok(());
2319            };
2320
2321            let mut text: Option<String> = None;
2322
2323            match clipboard.try_get_text_async().await {
2324                Ok(value) => text = value,
2325                Err(ex) if ClipboardHelper::is_expected_clipboard_exception(&ex) => {
2326                    if let Some(logger) = Logger::try_get(LogEventLevel::Warning, LogArea::CONTROL) {
2327                        let source: &dyn Any = &this;
2328                        logger.log_with_values(Some(source), "Failed to read text from clipboard: {Error}", &[&ex]);
2329                    }
2330                }
2331                Err(ex) => return Err(ex),
2332            }
2333
2334            let Some(text) = text.filter(|text| !text.is_empty()) else {
2335                return Ok(());
2336            };
2337
2338            this.snapshot_undo_redo(true);
2339            this.handle_text_input(Some(&text));
2340            Ok(())
2341        })
2342    }
2343
2344    fn select_word(&self, text: &[u16], caret_index: i32, mut selection_start: i32, mut selection_end: i32) {
2345        if self.is_password_box() {
2346            // Double-clicking in a cloaked single-line password box selects all text.
2347            self.word_selection_start.set(-1);
2348
2349            self.select_all();
2350        }
2351
2352        if !StringUtils::is_start_of_word(text, caret_index) {
2353            selection_start = StringUtils::previous_word(text, caret_index);
2354        }
2355
2356        if !StringUtils::is_end_of_word(text, caret_index) {
2357            selection_end = StringUtils::next_word(text, caret_index);
2358        }
2359
2360        if selection_start != selection_end {
2361            self.word_selection_start.set(selection_start);
2362        }
2363
2364        self.set_current_value(Self::selection_start_property(), selection_start);
2365        self.set_current_value(Self::selection_end_property(), selection_end);
2366    }
2367
2368    fn update_word_selection_range(&self, caret_index: i32, selection_start: &mut i32, selection_end: &mut i32) {
2369        let text = self.text_utf16();
2370
2371        if text.is_empty() {
2372            return;
2373        }
2374
2375        let word_selection_start = self.word_selection_start.get();
2376
2377        if caret_index > word_selection_start {
2378            let next_word = StringUtils::next_word(&text, caret_index);
2379
2380            *selection_end = next_word;
2381
2382            *selection_start = word_selection_start;
2383        } else {
2384            let previous_word = StringUtils::previous_word(&text, caret_index);
2385            *selection_start = previous_word;
2386
2387            *selection_end = StringUtils::next_word(&text, word_selection_start);
2388        }
2389    }
2390
2391    /// Clamps a text position to the text of `sender` and moves it out of
2392    /// the middle of a `\r\n` pair.
2393    ///
2394    /// Also used by the text presenter and the selectable text block.
2395    pub(crate) fn coerce_caret_index(sender: &FerroObject, value: i32) -> i32 {
2396        let text = match sender.downcast_ref::<SelectableTextBlock>() {
2397            Some(text_block) if text_block.has_complex_content() => {
2398                text_block.inlines().and_then(|inlines| inlines.text())
2399            }
2400            _ => sender.get_value(Self::text_property()),
2401        };
2402
2403        let Some(text) = text else {
2404            return 0;
2405        };
2406
2407        if value < 0 {
2408            return 0;
2409        }
2410
2411        let mut length = 0;
2412        let mut previous = None;
2413        let mut current = None;
2414
2415        for unit in text.encode_utf16() {
2416            if length == value - 1 {
2417                previous = Some(unit);
2418            } else if length == value {
2419                current = Some(unit);
2420            }
2421
2422            length += 1;
2423        }
2424
2425        if value > length {
2426            length
2427        } else if previous == Some(CARRIAGE_RETURN) && current == Some(LINE_FEED) {
2428            value + 1
2429        } else {
2430            value
2431        }
2432    }
2433
2434    /// Clears the text in the text box.
2435    pub fn clear(&self) {
2436        self.set_text_from_edit(Some(String::new()))
2437    }
2438
2439    fn move_horizontal(&self, direction: i32, whole_word: bool, is_selecting: bool, move_caret_position: bool) {
2440        let Some(presenter) = self.presenter() else {
2441            return;
2442        };
2443
2444        let _change = self.im_client.begin_change();
2445
2446        let selection_start = self.selection_start();
2447        let selection_end = self.selection_end();
2448
2449        let logical_direction = if direction > 0 { LogicalDirection::Forward } else { LogicalDirection::Backward };
2450
2451        if !whole_word {
2452            if is_selecting {
2453                presenter.move_caret_to_text_position(selection_end, false);
2454
2455                presenter.move_caret_horizontal(logical_direction);
2456
2457                self.set_current_value(Self::selection_end_property(), presenter.caret_index());
2458            } else {
2459                if selection_start != selection_end {
2460                    self.clear_selection_and_move_caret_to_text_position(logical_direction);
2461                } else {
2462                    presenter.move_caret_horizontal(logical_direction);
2463                }
2464
2465                self.set_current_value(Self::caret_index_property(), presenter.caret_index());
2466            }
2467        } else {
2468            let text = self.text_utf16();
2469
2470            let offset = if direction > 0 {
2471                StringUtils::next_word(&text, selection_end) - selection_end
2472            } else {
2473                StringUtils::previous_word(&text, selection_end) - selection_end
2474            };
2475
2476            self.set_current_value(Self::selection_end_property(), self.selection_end() + offset);
2477
2478            if move_caret_position {
2479                presenter.move_caret_to_text_position(self.selection_end(), false);
2480            }
2481
2482            if !is_selecting && move_caret_position {
2483                self.set_current_value(Self::caret_index_property(), self.selection_end());
2484            } else {
2485                self.set_current_value(Self::selection_start_property(), selection_start);
2486            }
2487        }
2488    }
2489
2490    fn move_vertical(&self, direction: LogicalDirection, is_selecting: bool) {
2491        let Some(presenter) = self.presenter() else {
2492            return;
2493        };
2494
2495        if is_selecting {
2496            let old_caret_index = presenter.caret_index();
2497            presenter.move_caret_vertical(direction);
2498            let new_caret_index = presenter.caret_index();
2499
2500            if old_caret_index == new_caret_index {
2501                let text_length = self.text_length();
2502
2503                // The caret did not move while we are selecting so we could not move to the previous/next
2504                // line, but check if we are already at the 'boundary' of the text.
2505                if direction == LogicalDirection::Forward && new_caret_index < text_length {
2506                    presenter.move_caret_to_text_position(text_length, false);
2507                } else if direction == LogicalDirection::Backward && new_caret_index > 0 {
2508                    presenter.move_caret_to_text_position(0, false);
2509                }
2510            }
2511
2512            self.set_current_value(Self::selection_end_property(), presenter.caret_index());
2513        } else {
2514            if self.selection_start() != self.selection_end() {
2515                self.clear_selection_and_move_caret_to_text_position(direction);
2516            }
2517
2518            presenter.move_caret_vertical(direction);
2519
2520            self.set_current_value(Self::caret_index_property(), presenter.caret_index());
2521        }
2522    }
2523
2524    fn move_home(&self, document: bool) {
2525        let Some(presenter) = self.presenter() else {
2526            return;
2527        };
2528
2529        let caret_index = self.caret_index();
2530
2531        if document {
2532            presenter.move_caret_to_text_position(0, false);
2533        } else {
2534            let text_layout = presenter.text_layout();
2535            let line_index = text_layout.get_line_index_from_character_index(caret_index, false);
2536            let text_position = text_layout.text_lines()[line_index as usize].first_text_source_index();
2537
2538            presenter.move_caret_to_text_position(text_position, false);
2539        }
2540    }
2541
2542    fn move_end(&self, document: bool) {
2543        let Some(presenter) = self.presenter() else {
2544            return;
2545        };
2546
2547        let caret_index = self.caret_index();
2548
2549        if document {
2550            presenter.move_caret_to_text_position(self.text_length(), true);
2551        } else {
2552            let text_layout = presenter.text_layout();
2553            let line_index = text_layout.get_line_index_from_character_index(caret_index, false);
2554            let text_line = &text_layout.text_lines()[line_index as usize];
2555
2556            let text_position = text_line.first_text_source_index() + text_line.length() - text_line.new_line_length();
2557
2558            presenter.move_caret_to_text_position(text_position, true);
2559        }
2560    }
2561
2562    fn move_page_right(&self) {
2563        if let Some(scroll_viewer) = self.scroll_viewer() {
2564            scroll_viewer.page_right();
2565        }
2566    }
2567
2568    fn move_page_left(&self) {
2569        if let Some(scroll_viewer) = self.scroll_viewer() {
2570            scroll_viewer.page_left();
2571        }
2572    }
2573
2574    fn move_page_up(&self) {
2575        if let Some(scroll_viewer) = self.scroll_viewer() {
2576            scroll_viewer.page_up();
2577        }
2578    }
2579
2580    fn move_page_down(&self) {
2581        if let Some(scroll_viewer) = self.scroll_viewer() {
2582            scroll_viewer.page_down();
2583        }
2584    }
2585
2586    fn clear_selection_and_move_caret_to_text_position(&self, direction: LogicalDirection) {
2587        let new_position = if direction == LogicalDirection::Forward {
2588            self.selection_start().max(self.selection_end())
2589        } else {
2590            self.selection_start().min(self.selection_end())
2591        };
2592        self.set_current_value(Self::selection_start_property(), new_position);
2593        self.set_current_value(Self::selection_end_property(), new_position);
2594        // Move the caret to the appropriate side of the previous selection.
2595        if let Some(presenter) = self.presenter() {
2596            presenter.move_caret_to_text_position(new_position, false);
2597        }
2598    }
2599
2600    /// Scrolls the text box to the specified line index.
2601    ///
2602    /// # Panics
2603    ///
2604    /// When `line_index` is less than zero, or larger than or equal to the
2605    /// line count.
2606    pub fn scroll_to_line(&self, line_index: i32) {
2607        let Some(presenter) = self.presenter() else {
2608            return;
2609        };
2610
2611        let text_layout = presenter.text_layout();
2612
2613        if line_index < 0 || line_index >= text_layout.text_lines().len() as i32 {
2614            panic!("Specified argument was out of the range of valid values. (Parameter 'lineIndex')");
2615        }
2616
2617        let text_position = text_layout.text_lines()[line_index as usize].first_text_source_index();
2618        presenter.move_caret_to_text_position(text_position, false);
2619    }
2620
2621    /// Selects all text in the text box.
2622    pub fn select_all(&self) {
2623        let _change = self.im_client.begin_change();
2624
2625        self.set_current_value(Self::selection_start_property(), 0);
2626        self.set_current_value(Self::selection_end_property(), self.text_length());
2627    }
2628
2629    fn get_selection_range(&self) -> (i32, i32) {
2630        let selection_start = self.selection_start();
2631        let selection_end = self.selection_end();
2632
2633        (selection_start.min(selection_end), selection_start.max(selection_end))
2634    }
2635
2636    /// Deletes the selected text. Returns whether there was a selection (or
2637    /// the text box is read-only).
2638    pub(crate) fn delete_selection(&self) -> bool {
2639        if self.is_read_only() {
2640            return true;
2641        }
2642
2643        let _change = self.im_client.begin_change();
2644
2645        let (start, end) = self.get_selection_range();
2646
2647        if start != end {
2648            let mut text = self.text_utf16();
2649
2650            remove_range(&mut text, start, end - start);
2651
2652            self.set_text_from_edit(Some(String::from_utf16_lossy(&text)));
2653
2654            if let Some(presenter) = self.presenter() {
2655                presenter.move_caret_to_text_position(start, false);
2656            }
2657
2658            self.set_current_value(Self::selection_start_property(), start);
2659
2660            self.clear_selection();
2661
2662            return true;
2663        }
2664
2665        self.set_current_value(Self::caret_index_property(), self.selection_start());
2666
2667        false
2668    }
2669
2670    /// Reports the same emptiness conditions as `get_selection`, without
2671    /// building the selected string.
2672    fn has_selection(&self) -> bool {
2673        let (start, end) = self.get_selection_range();
2674
2675        if start == end {
2676            return false;
2677        }
2678
2679        let text_length = self.text_length();
2680
2681        text_length > 0 && end <= text_length
2682    }
2683
2684    fn get_selection(&self) -> String {
2685        let Some(text) = self.text().filter(|text| !text.is_empty()) else {
2686            return String::new();
2687        };
2688
2689        let selection_start = self.selection_start();
2690        let selection_end = self.selection_end();
2691        let start = selection_start.min(selection_end);
2692        let end = selection_start.max(selection_end);
2693
2694        if start == end || utf16_length(&text) < end {
2695            return String::new();
2696        }
2697
2698        // The indices are UTF-16 code units; a selection that splits a
2699        // surrogate pair yields a replacement character for the lone half.
2700        let selected_text: Vec<u16> =
2701            text.encode_utf16().skip(start.max(0) as usize).take((end - start) as usize).collect();
2702
2703        String::from_utf16_lossy(&selected_text)
2704    }
2705
2706    /// Sets the text as an edit of the user: the undo history is kept.
2707    pub(crate) fn set_text_from_edit(&self, value: Option<String>) {
2708        self.set_text_core(value, TextMutationKind::Edit)
2709    }
2710
2711    /// Sets the text as a synchronization of the control with itself: the
2712    /// undo history is neither extended nor cleared.
2713    pub(crate) fn set_text_from_internal_synchronization(&self, value: Option<String>) {
2714        self.set_text_core(value, TextMutationKind::InternalSynchronization)
2715    }
2716
2717    fn set_text_core(&self, value: Option<String>, mutation_kind: TextMutationKind) {
2718        // Stays set through the synchronous TwoWay source echo so it isn't mistaken for an external replacement.
2719        let previous_mutation_kind = self.text_mutation_kind.replace(mutation_kind);
2720
2721        // Restores the mutation kind when the scope ends, also by a panic of a handler.
2722        struct Restore<'a>(&'a Cell<TextMutationKind>, TextMutationKind);
2723
2724        impl Drop for Restore<'_> {
2725            fn drop(&mut self) {
2726                self.0.set(self.1);
2727            }
2728        }
2729
2730        let _restore = Restore(&self.text_mutation_kind, previous_mutation_kind);
2731
2732        self.set_current_value(Self::text_property(), value);
2733    }
2734
2735    /// Returns the sum of any vertical whitespace added between the
2736    /// [`ScrollViewer`] and the [`TextPresenter`] in the control template.
2737    fn get_vertical_space_between_scroll_viewer_and_presenter(&self) -> f64 {
2738        let mut vertical_space = 0.0;
2739
2740        if let Some(presenter) = self.presenter() {
2741            let scroll_viewer: Option<Ref<Visual>> = self.scroll_viewer().map(Ref::upcast);
2742            let this: Ref<Visual> = self.to_ref().upcast();
2743            let mut visual: Option<Ref<Visual>> = Some(presenter.upcast());
2744
2745            while let Some(current) = visual.filter(|visual| *visual != this) {
2746                if Some(&current) == scroll_viewer.as_ref() {
2747                    // The scroll viewer is a stopping point and should only include the padding.
2748                    let padding: Thickness = current.get_value(Decorator::padding_property());
2749                    vertical_space += padding.top + padding.bottom;
2750                    break;
2751                }
2752
2753                let margin: Thickness = current.get_value(Layoutable::margin_property());
2754                let padding: Thickness = current.get_value(Decorator::padding_property());
2755
2756                vertical_space += margin.top + padding.top + padding.bottom + margin.bottom;
2757
2758                visual = current.visual_parent();
2759            }
2760        }
2761
2762        vertical_space
2763    }
2764
2765    /// The layout of `lines` empty lines in the font of the text box.
2766    fn create_lines_text_layout(&self, lines: i32) -> TextLayout {
2767        let font_size = self.font_size();
2768        let typeface =
2769            Typeface::with_style(self.font_family(), self.font_style(), self.font_weight(), self.font_stretch());
2770
2771        let text_run_style: Rc<dyn TextRunProperties> = Rc::new(GenericTextRunProperties::with_all(
2772            typeface,
2773            font_size,
2774            None,
2775            None,
2776            None,
2777            BaselineAlignment::Baseline,
2778            None,
2779            self.font_features(),
2780        ));
2781
2782        let paragraph_properties: Rc<dyn TextParagraphProperties> = Rc::new(GenericTextParagraphProperties::with_all(
2783            FlowDirection::LeftToRight,
2784            TextAlignment::Left,
2785            true,
2786            false,
2787            text_run_style,
2788            TextWrapping::NoWrap,
2789            self.line_height(),
2790            0.0,
2791            0.0,
2792        ));
2793
2794        TextLayout::from_text_source(
2795            Rc::new(LineTextSource { lines }),
2796            paragraph_properties,
2797            None,
2798            f64::INFINITY,
2799            f64::INFINITY,
2800            0,
2801            None,
2802        )
2803    }
2804
2805    /// Raises both the `TextChanging` and `TextChanged` events.
2806    ///
2807    /// This must be called after the `Text` property is set.
2808    fn raise_text_change_events(&self) {
2809        // Note the following sequence of these events:
2810        // 1. TextChanging occurs synchronously when text starts to change but before it is rendered.
2811        //    This occurs after the Text property is set.
2812        // 2. TextChanged occurs asynchronously after text changes and the new text is rendered.
2813
2814        let text_changing_event_args = TextChangingEventArgs::with_event(Self::text_changing_event());
2815        self.raise_event(&text_changing_event_args);
2816
2817        let this = self.to_ref();
2818
2819        Dispatcher::ui_thread().post_local(
2820            move || {
2821                let text_changed_event_args = TextChangedEventArgs::with_event(Self::text_changed_event());
2822                this.raise_event(&text_changed_event_args);
2823            },
2824            DispatcherPriority::NORMAL,
2825        );
2826    }
2827
2828    fn set_selection_for_control_backspace(&self) {
2829        let text = self.text_utf16();
2830        let selection_start = self.caret_index();
2831
2832        let _change = self.im_client.begin_change();
2833
2834        self.move_horizontal(-1, true, false, false);
2835
2836        if self.selection_end() > 0
2837            && selection_start < text.len() as i32
2838            && text.get(selection_start.max(0) as usize) == Some(&SPACE)
2839        {
2840            self.set_current_value(Self::selection_end_property(), self.selection_end() - 1);
2841        }
2842
2843        self.set_current_value(Self::selection_start_property(), selection_start);
2844    }
2845
2846    fn set_selection_for_control_delete(&self) {
2847        let text = self.text_utf16();
2848        let text_length = text.len() as i32;
2849        if self.presenter().is_none() || text_length == 0 {
2850            return;
2851        }
2852
2853        let _change = self.im_client.begin_change();
2854
2855        self.set_current_value(Self::selection_start_property(), self.caret_index());
2856
2857        self.move_horizontal(1, true, true, false);
2858
2859        let selection_end = self.selection_end();
2860
2861        if selection_end < text_length && text.get(selection_end.max(0) as usize) == Some(&SPACE) {
2862            self.set_current_value(Self::selection_end_property(), selection_end + 1);
2863        }
2864    }
2865
2866    fn update_pseudoclasses(&self) {
2867        self.pseudo_classes().set(":empty", self.text().is_none_or(|text| text.is_empty()));
2868        self.pseudo_classes().set(":touch-mode", self.is_in_touch_mode.get());
2869    }
2870
2871    fn is_password_box(&self) -> bool {
2872        self.password_char() != '\0' && !self.reveal_password()
2873    }
2874
2875    fn snapshot_undo_redo(&self, ignore_change_count: bool) {
2876        if self.is_undo_enabled()
2877            && (ignore_change_count
2878                || !self.has_done_snapshot_once.get()
2879                || self.selected_text_changes_made_since_last_undo_snapshot.get() >= MAX_CHARS_BEFORE_UNDO_SNAPSHOT)
2880        {
2881            self.undo_redo_helper.snapshot();
2882            self.selected_text_changes_made_since_last_undo_snapshot.set(0);
2883            self.has_done_snapshot_once.set(true);
2884        }
2885    }
2886
2887    fn clear_undo_redo(&self) {
2888        self.undo_redo_helper.clear();
2889        self.selected_text_changes_made_since_last_undo_snapshot.set(0);
2890        self.has_done_snapshot_once.set(false);
2891    }
2892
2893    /// Undoes the first action in the undo stack.
2894    pub fn undo(&self) {
2895        if self.is_undo_enabled() && self.can_undo() {
2896            // Snapshot the current Text state - this will get popped on to the redo stack
2897            // when we call undo below.
2898            self.snapshot_undo_redo(true);
2899
2900            let _undoing_redoing = UndoingRedoing::enter(&self.is_undoing_redoing);
2901            self.undo_redo_helper.undo();
2902        }
2903    }
2904
2905    /// Reapplies the first item on the redo stack.
2906    pub fn redo(&self) {
2907        if self.is_undo_enabled() && self.can_redo() {
2908            let _undoing_redoing = UndoingRedoing::enter(&self.is_undoing_redoing);
2909            self.undo_redo_helper.redo();
2910        }
2911    }
2912}
2913
2914/// The scope of an undo or a redo: the flag is cleared when it ends.
2915struct UndoingRedoing<'a>(&'a Cell<bool>);
2916
2917impl<'a> UndoingRedoing<'a> {
2918    fn enter(flag: &'a Cell<bool>) -> Self {
2919        flag.set(true);
2920        Self(flag)
2921    }
2922}
2923
2924impl Drop for UndoingRedoing<'_> {
2925    fn drop(&mut self) {
2926        self.0.set(false);
2927    }
2928}
2929
2930/// The line break of the platform.
2931fn environment_new_line() -> &'static str {
2932    if cfg!(windows) {
2933        "\r\n"
2934    } else {
2935        "\n"
2936    }
2937}
2938
2939/// The length of `text` in UTF-16 code units.
2940fn utf16_length(text: &str) -> i32 {
2941    text.encode_utf16().count() as i32
2942}
2943
2944/// Removes `length` code units at `start`, clamped to the text.
2945fn remove_range(text: &mut Vec<u16>, start: i32, length: i32) {
2946    let start = (start.max(0) as usize).min(text.len());
2947    let end = (start + length.max(0) as usize).min(text.len());
2948
2949    text.drain(start..end);
2950}
2951
2952/// Whether the pointer of a pointer event is captured by the presenter.
2953fn is_captured_by(e: &PointerEventArgs, presenter: &Ref<TextPresenter>) -> bool {
2954    e.pointer().captured().is_some_and(|captured| captured == *presenter)
2955}
2956
2957/// Clamps a point to the bounds of the presenter.
2958fn clamp_to_bounds(point: Point, presenter: &TextPresenter) -> Point {
2959    let bounds = presenter.bounds();
2960
2961    Point::new(
2962        MathUtilities::clamp(point.x, 0.0, (bounds.width - 1.0).max(0.0)),
2963        MathUtilities::clamp(point.y, 0.0, (bounds.height - 1.0).max(0.0)),
2964    )
2965}