Skip to main content

ferroui_controls/
text_box.rs

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