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