1use crate::documents::TextElement;
2use crate::primitives::{TextSelectionHandleCanvas, TextSelectorLayer};
3use crate::{Border, Control, ControlImpl, TextBlock, TextBox, TextBoxTextInputMethodClient};
4use ferroui_base::animation::TimeSpan;
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{LayoutableImpl, VerticalAlignment};
8use ferroui_base::media::immutable::{ImmutablePen, ImmutableSolidColorBrush};
9use ferroui_base::media::text_formatting::{
10 GenericTextRunProperties, LogicalDirection, TextLayout, TextLayoutOptions, TextRunCache, TextRunProperties,
11};
12use ferroui_base::media::{
13 BaselineAlignment, BrushExtensions, Brushes, CharacterHit, Color, DrawingContext, FlowDirection, FontFamily,
14 FontFeatureCollection, FontStretch, FontStyle, FontWeight, IBrush, IImmutableBrush, IPen, TextAlignment,
15 TextDecorations, TextWrapping, Typeface,
16};
17use ferroui_base::reactive::{Disposable, IDisposable};
18use ferroui_base::threading::{Dispatcher, DispatcherPriority, DispatcherTimer};
19use ferroui_base::utilities::{HandlerList, MathUtilities, ReadOnlyMemory, ValueSpan};
20use ferroui_base::{
21 ferro_class, ferro_property, instantiate, AttachedProperty, FerroObjectImpl, FerroObjectImplExt,
22 FerroProperty, FerroPropertyChangedEventArgs, PixelRect, Point, Rect, Ref, Size, StyledElementImpl,
23 StyledProperty, StyledPropertyMetadata, Visual, VisualImpl, VisualImplExt,
24 VisualTreeAttachmentEventArgs, WeakRef,
25};
26use std::cell::{Cell, RefCell};
27use std::rc::{Rc, Weak};
28
29#[repr(C)]
35pub struct TextPresenter {
36 base: Control,
37 caret_timer: RefCell<Option<Rc<DispatcherTimer>>>,
38 caret_timer_tick: RefCell<Option<Rc<dyn IDisposable>>>,
40 caret_blink: Cell<bool>,
41 text_layout: RefCell<Option<Rc<TextLayout>>>,
46 text_run_cache: RefCell<Option<Rc<TextRunCache>>>,
47 constraint: Cell<Size>,
48
49 last_character_hit: Cell<CharacterHit>,
50 caret_bounds: Cell<Rect>,
51 caret_bounds_dirty: Cell<bool>,
52 pending_caret_text_position: Cell<Option<i32>>,
53 navigation_position: Cell<Point>,
54 previous_offset: Cell<Option<Point>>,
55 layer: RefCell<Option<WeakRef<TextSelectorLayer>>>,
58 text_selection_handle_canvas: RefCell<Option<Ref<TextSelectionHandleCanvas>>>,
59 caret_bounds_changed: HandlerList<dyn Fn()>,
60 current_im_client: RefCell<Option<Weak<TextBoxTextInputMethodClient>>>,
61 utf16_text: RefCell<Option<Option<ReadOnlyMemory<u16>>>>,
64}
65
66ferro_class! {
67 TextPresenter: Control, virtuals TextPresenterImpl: ControlImpl {
68 fn create_text_layout(this) -> Rc<TextLayout>;
70 fn invalidate_text_layout(this);
72 }
73}
74ferroui_base::ferro_class_info!(TextPresenter { new: TextPresenter::new });
75
76ferroui_base::ferro_impl_classes!(TextPresenter: InteractiveImpl);
77ferroui_base::ferro_impl_classes!(TextPresenter: InputElementImpl);
78ferroui_base::ferro_impl_classes!(TextPresenter: ControlImpl);
79ferroui_base::ferro_impl_classes!(TextPresenter: StyledElementImpl);
80
81impl FerroObjectImpl for TextPresenter {
82 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
83 let property = change.property();
84
85 if property == Self::text_property().as_property() {
86 *this.utf16_text.borrow_mut() = None;
87 }
88
89 Self::parent_on_property_changed(this, change);
90
91 if property == Self::caret_index_property().as_property() {
92 this.move_caret_to_text_position(change.get_new_value::<i32>(), false);
93 }
94
95 if (property == Self::text_property().as_property() || property == Self::caret_index_property().as_property())
96 && this.preedit_text().is_some_and(|preedit_text| !preedit_text.is_empty())
97 {
98 this.set_current_value(Self::preedit_text_property(), None);
99 }
100
101 if property == Self::caret_blink_interval_property().as_property() {
102 this.reset_caret_timer();
103 }
104
105 match property.name() {
106 "PreeditText" | "Foreground" | "FontSize" | "FontStyle" | "FontWeight" | "FontFamily" | "FontStretch"
108 | "Text" | "LetterSpacing" | "PasswordChar" | "RevealPassword" | "FlowDirection"
109 | "SelectionForegroundBrush" | "ShowSelectionHighlight" => {
110 this.invalidate_text_layout();
111 }
112 "TextAlignment" | "TextWrapping" | "LineHeight" => {
114 this.invalidate_text_layout_keep_cache();
115 }
116 "SelectionStart" | "SelectionEnd" => {
117 if this.selection_foreground_brush().is_some() {
118 this.invalidate_text_layout();
119 } else {
120 this.invalidate_text_layout_keep_cache();
121 }
122 }
123 _ => {}
124 }
125
126 if property == Self::preedit_text_property().as_property() {
130 let preedit_text = change.get_new_value::<Option<String>>();
131 this.on_preedit_changed(preedit_text.as_deref(), this.preedit_text_cursor_position());
132 }
133
134 if property == Self::preedit_text_cursor_position_property().as_property() {
135 let preedit_text = this.preedit_text();
136 this.on_preedit_changed(preedit_text.as_deref(), this.preedit_text_cursor_position());
137 }
138 }
139}
140
141impl VisualImpl for TextPresenter {
142 fn render(this: &Self, context: &mut DrawingContext) {
146 let selection_start = this.selection_start();
147 let selection_end = this.selection_end();
148 let selection_brush = this.selection_brush();
149
150 if let Some(selection_brush) = selection_brush {
151 if this.show_selection_highlight() && selection_start != selection_end {
152 let start = selection_start.min(selection_end);
153 let length = selection_start.max(selection_end) - start;
154
155 let rects = this.text_layout().hit_test_text_range(start, length);
156
157 for rect in rects {
158 context.fill_rectangle(&selection_brush, PixelRect::from_rect(rect, 1.0).to_rect(1.0), 0.0);
159 }
160 }
161 }
162
163 if let Some(root) = this.visual_root() {
164 let offset = this.translate_point(this.bounds().position(), &root);
165
166 if this.previous_offset.get() != offset {
167 this.previous_offset.set(offset);
168 }
169 }
170
171 this.render_internal(context);
172
173 if selection_start != selection_end || !this.caret_blink.get() {
174 return;
175 }
176
177 let caret_brush: Rc<dyn IImmutableBrush> = match this.caret_brush() {
178 Some(caret_brush) => BrushExtensions::to_immutable(&caret_brush),
179 None => {
180 let background_color = this
181 .background()
182 .and_then(|background| background.as_solid_color_brush().map(|brush| brush.color()));
183
184 match background_color {
185 Some(background_color) => {
186 let red = !background_color.r;
187 let green = !background_color.g;
188 let blue = !background_color.b;
189
190 Rc::new(ImmutableSolidColorBrush::new(Color::from_rgb(red, green, blue)))
191 }
192 None => Brushes::black(),
193 }
194 }
195 };
196
197 let (p1, p2) = this.get_caret_points();
198
199 let pen: Rc<dyn IPen> = Rc::new(ImmutablePen::with_brush(Some(caret_brush), 1.0));
200
201 context.draw_line(&pen, p1, p2);
202 }
203
204 fn bypass_flow_direction_policies(_this: &Self) -> bool {
205 true
206 }
207
208 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
209 Self::parent_on_attached_to_visual_tree(this, e);
210
211 this.reset_caret_timer();
212 }
213
214 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
215 Self::parent_on_detached_from_visual_tree(this, e);
216
217 this.remove_text_selection_canvas();
218
219 let caret_timer = this.caret_timer.borrow().clone();
220
221 if let Some(caret_timer) = caret_timer {
222 caret_timer.stop();
223 this.unsubscribe_caret_timer_tick();
224 }
225 }
226}
227
228impl LayoutableImpl for TextPresenter {
229 fn measure_override(this: &Self, available_size: Size) -> Size {
230 this.constraint.set(available_size);
231
232 this.dispose_text_layout();
233
234 this.invalidate_arrange();
235
236 let text_layout = this.text_layout();
237
238 let size = Size::new(text_layout.width_including_trailing_whitespace(), text_layout.height());
241
242 this.ensure_caret_bounds();
243
244 size
245 }
246
247 fn arrange_override(this: &Self, mut final_size: Size) -> Size {
248 let final_width = final_size.width;
249
250 let text_width = this.text_layout().width_including_trailing_whitespace().ceil();
251
252 if final_size.width < text_width {
253 final_size = final_size.with_width(text_width);
254 }
255
256 if !MathUtilities::are_close(this.constraint.get().width, final_width) {
261 this.constraint.set(Size::new(final_width.ceil(), f64::INFINITY));
262
263 this.dispose_text_layout();
264
265 let _ = this.text_layout();
268 this.ensure_caret_bounds();
269 }
270
271 final_size
272 }
273}
274
275impl TextPresenterImpl for TextPresenter {
276 fn create_text_layout(this: &Self) -> Rc<TextLayout> {
277 let caret_index = this.caret_index();
278 let preedit_text = this.preedit_text().filter(|preedit_text| !preedit_text.is_empty());
279 let preedit_text = preedit_text.map(|preedit_text| ReadOnlyMemory::<u16>::from_str(&preedit_text));
280 let text = Self::get_combined_text(this.utf16_text(), caret_index, preedit_text.as_ref());
281 let typeface =
282 Typeface::with_style(this.font_family(), this.font_style(), this.font_weight(), this.font_stretch());
283 let selection_start = this.selection_start();
284 let selection_end = this.selection_end();
285 let start = selection_start.min(selection_end);
286 let length = selection_start.max(selection_end) - start;
287
288 let mut text_style_overrides: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>> = None;
289
290 let foreground = this.foreground();
291
292 if let Some(preedit_text) = &preedit_text {
293 let properties: Rc<dyn TextRunProperties> = Rc::new(GenericTextRunProperties::with_all(
294 typeface.clone(),
295 this.font_size(),
296 Some(TextDecorations::underline()),
297 foreground,
298 None,
299 BaselineAlignment::Baseline,
300 None,
301 this.font_features(),
302 ));
303
304 let preedit_highlight = ValueSpan::new(caret_index, preedit_text.len() as i32, properties);
305
306 text_style_overrides = Some(Rc::new([preedit_highlight]));
307 } else if this.show_selection_highlight() && length > 0 {
308 if let Some(selection_foreground_brush) = this.selection_foreground_brush() {
309 let properties: Rc<dyn TextRunProperties> = Rc::new(GenericTextRunProperties::with_all(
310 typeface.clone(),
311 this.font_size(),
312 None,
313 Some(selection_foreground_brush),
314 None,
315 BaselineAlignment::Baseline,
316 None,
317 this.font_features(),
318 ));
319
320 text_style_overrides = Some(Rc::new([ValueSpan::new(start, length, properties)]));
321 }
322 }
323
324 let password_char = this.password_char();
325
326 if password_char != '\0' && !this.reveal_password() {
327 let length = text.as_ref().map_or(0, ReadOnlyMemory::len);
328 let mut units = [0u16; 2];
329 let units = password_char.encode_utf16(&mut units);
330 let mut password = Vec::with_capacity(length * units.len());
331
332 for _ in 0..length {
333 password.extend_from_slice(units);
334 }
335
336 this.create_text_layout_internal(
337 this.constraint.get(),
338 Some(ReadOnlyMemory::from_vec(password)),
339 typeface,
340 text_style_overrides,
341 )
342 } else {
343 this.create_text_layout_internal(this.constraint.get(), text, typeface, text_style_overrides)
344 }
345 }
346
347 fn invalidate_text_layout(this: &Self) {
348 let text_run_cache = this.text_run_cache.borrow().clone();
349
350 if let Some(text_run_cache) = text_run_cache {
351 text_run_cache.invalidate();
352 }
353
354 this.dispose_text_layout();
355
356 this.invalidate_visual();
357 this.invalidate_measure();
358 }
359}
360
361ferroui_base::ferro_properties! { impl TextPresenter {
362 ferro_property!(
363 pub fn show_selection_highlight_property() -> StyledProperty<bool> {
365 FerroProperty::register::<TextPresenter, _>("ShowSelectionHighlight", true)
366 }
367 );
368
369 ferro_property!(
370 pub fn caret_index_property() -> StyledProperty<i32> {
372 TextBox::caret_index_property()
373 .add_owner_with::<TextPresenter>(StyledPropertyMetadata::new(None).with_coerce(TextBox::coerce_caret_index))
374 }
375 );
376
377 ferro_property!(
378 pub fn reveal_password_property() -> StyledProperty<bool> {
380 FerroProperty::register::<TextPresenter, _>("RevealPassword", false)
381 }
382 );
383
384 ferro_property!(
385 pub fn password_char_property() -> StyledProperty<char> {
387 FerroProperty::register::<TextPresenter, _>("PasswordChar", '\0')
388 }
389 );
390
391 ferro_property!(
392 pub fn selection_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
394 FerroProperty::register::<TextPresenter, _>("SelectionBrush", None)
395 }
396 );
397
398 ferro_property!(
399 pub fn selection_foreground_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
401 FerroProperty::register::<TextPresenter, _>("SelectionForegroundBrush", None)
402 }
403 );
404
405 ferro_property!(
406 pub fn caret_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
408 FerroProperty::register::<TextPresenter, _>("CaretBrush", None)
409 }
410 );
411
412 ferro_property!(
413 pub fn caret_blink_interval_property() -> StyledProperty<TimeSpan> {
415 TextBox::caret_blink_interval_property().add_owner::<TextPresenter>()
416 }
417 );
418
419 ferro_property!(
420 pub fn selection_start_property() -> StyledProperty<i32> {
422 TextBox::selection_start_property()
423 .add_owner_with::<TextPresenter>(StyledPropertyMetadata::new(None).with_coerce(TextBox::coerce_caret_index))
424 }
425 );
426
427 ferro_property!(
428 pub fn selection_end_property() -> StyledProperty<i32> {
430 TextBox::selection_end_property()
431 .add_owner_with::<TextPresenter>(StyledPropertyMetadata::new(None).with_coerce(TextBox::coerce_caret_index))
432 }
433 );
434
435 ferro_property!(
436 pub fn text_property() -> StyledProperty<Option<String>> {
438 TextBlock::text_property()
439 .add_owner_with::<TextPresenter>(StyledPropertyMetadata::new(Some(Some(String::new()))))
440 }
441 );
442
443 ferro_property!(
444 pub fn preedit_text_property() -> StyledProperty<Option<String>> {
446 FerroProperty::register::<TextPresenter, _>("PreeditText", None)
447 }
448 );
449
450 ferro_property!(
451 pub fn preedit_text_cursor_position_property() -> StyledProperty<Option<i32>> {
453 FerroProperty::register::<TextPresenter, _>("PreeditTextCursorPosition", None)
454 }
455 );
456
457 ferro_property!(
458 pub fn text_alignment_property() -> AttachedProperty<TextAlignment> {
460 TextBlock::text_alignment_property().add_owner::<TextPresenter>()
461 }
462 );
463
464 ferro_property!(
465 pub fn text_wrapping_property() -> AttachedProperty<TextWrapping> {
467 TextBlock::text_wrapping_property().add_owner::<TextPresenter>()
468 }
469 );
470
471 ferro_property!(
472 pub fn line_height_property() -> AttachedProperty<f64> {
474 TextBlock::line_height_property().add_owner::<TextPresenter>()
475 }
476 );
477
478 ferro_property!(
479 pub fn letter_spacing_property() -> AttachedProperty<f64> {
481 TextElement::letter_spacing_property().add_owner::<TextPresenter>()
482 }
483 );
484
485 ferro_property!(
486 pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
488 Border::background_property().add_owner::<TextPresenter>()
489 }
490 );
491} }
492
493impl TextPresenter {
494 fn static_constructor() {
495 Visual::affects_render::<TextPresenter>(&[
496 Self::caret_brush_property().as_property(),
497 Self::selection_brush_property().as_property(),
498 Self::selection_foreground_brush_property().as_property(),
499 TextElement::foreground_property().as_property(),
500 Self::show_selection_highlight_property().as_property(),
501 ]);
502 }
503
504 pub fn construct() -> Self {
506 Self {
507 base: Control::construct(),
508 caret_timer: RefCell::new(None),
509 caret_timer_tick: RefCell::new(None),
510 caret_blink: Cell::new(false),
511 text_layout: RefCell::new(None),
512 text_run_cache: RefCell::new(None),
513 constraint: Cell::new(Size::default()),
514 last_character_hit: Cell::new(CharacterHit::default()),
515 caret_bounds: Cell::new(Rect::default()),
516 caret_bounds_dirty: Cell::new(false),
517 pending_caret_text_position: Cell::new(None),
518 navigation_position: Cell::new(Point::default()),
519 previous_offset: Cell::new(None),
520 layer: RefCell::new(None),
521 text_selection_handle_canvas: RefCell::new(None),
522 caret_bounds_changed: HandlerList::new(),
523 current_im_client: RefCell::new(None),
524 utf16_text: RefCell::new(None),
525 }
526 }
527
528 pub fn new() -> Ref<Self> {
530 instantiate(Self::construct())
531 }
532
533 pub fn caret_bounds_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
535 let token = self.caret_bounds_changed.add(Rc::new(handler));
536 let weak = self.to_ref().downgrade();
537 Disposable::create(move || {
538 if let Some(this) = weak.upgrade() {
539 this.caret_bounds_changed.remove(token);
540 }
541 })
542 }
543
544 pub fn background(&self) -> Option<Rc<dyn IBrush>> {
546 self.get_value(Self::background_property())
547 }
548
549 pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
550 self.set_value(Self::background_property(), value)
551 }
552
553 pub fn show_selection_highlight(&self) -> bool {
555 self.get_value(Self::show_selection_highlight_property())
556 }
557
558 pub fn set_show_selection_highlight(&self, value: bool) {
559 self.set_value(Self::show_selection_highlight_property(), value)
560 }
561
562 pub fn text(&self) -> Option<String> {
564 self.get_value(Self::text_property())
565 }
566
567 pub fn set_text(&self, value: Option<&str>) {
568 self.set_value(Self::text_property(), value.map(str::to_owned))
569 }
570
571 pub fn preedit_text(&self) -> Option<String> {
573 self.get_value(Self::preedit_text_property())
574 }
575
576 pub fn set_preedit_text(&self, value: Option<&str>) {
577 self.set_value(Self::preedit_text_property(), value.map(str::to_owned))
578 }
579
580 pub fn preedit_text_cursor_position(&self) -> Option<i32> {
582 self.get_value(Self::preedit_text_cursor_position_property())
583 }
584
585 pub fn set_preedit_text_cursor_position(&self, value: Option<i32>) {
586 self.set_value(Self::preedit_text_cursor_position_property(), value)
587 }
588
589 pub fn font_family(&self) -> FontFamily {
591 TextElement::get_font_family(self)
592 }
593
594 pub fn set_font_family(&self, value: FontFamily) {
595 TextElement::set_font_family_on(self, value)
596 }
597
598 pub fn font_features(&self) -> Option<FontFeatureCollection> {
600 TextElement::get_font_features(self)
601 }
602
603 pub fn set_font_features(&self, value: Option<FontFeatureCollection>) {
604 TextElement::set_font_features_on(self, value)
605 }
606
607 pub fn font_size(&self) -> f64 {
609 TextElement::get_font_size(self)
610 }
611
612 pub fn set_font_size(&self, value: f64) {
613 TextElement::set_font_size_on(self, value)
614 }
615
616 pub fn font_style(&self) -> FontStyle {
618 TextElement::get_font_style(self)
619 }
620
621 pub fn set_font_style(&self, value: FontStyle) {
622 TextElement::set_font_style_on(self, value)
623 }
624
625 pub fn font_weight(&self) -> FontWeight {
627 TextElement::get_font_weight(self)
628 }
629
630 pub fn set_font_weight(&self, value: FontWeight) {
631 TextElement::set_font_weight_on(self, value)
632 }
633
634 pub fn font_stretch(&self) -> FontStretch {
636 TextElement::get_font_stretch(self)
637 }
638
639 pub fn set_font_stretch(&self, value: FontStretch) {
640 TextElement::set_font_stretch_on(self, value)
641 }
642
643 pub fn foreground(&self) -> Option<Rc<dyn IBrush>> {
645 TextElement::get_foreground(self)
646 }
647
648 pub fn set_foreground(&self, value: Option<Rc<dyn IBrush>>) {
649 TextElement::set_foreground_on(self, value)
650 }
651
652 pub fn text_wrapping(&self) -> TextWrapping {
654 self.get_value(Self::text_wrapping_property())
655 }
656
657 pub fn set_text_wrapping(&self, value: TextWrapping) {
658 self.set_value(Self::text_wrapping_property(), value)
659 }
660
661 pub fn line_height(&self) -> f64 {
664 self.get_value(Self::line_height_property())
665 }
666
667 pub fn set_line_height(&self, value: f64) {
668 self.set_value(Self::line_height_property(), value)
669 }
670
671 pub fn letter_spacing(&self) -> f64 {
673 self.get_value(Self::letter_spacing_property())
674 }
675
676 pub fn set_letter_spacing(&self, value: f64) {
677 self.set_value(Self::letter_spacing_property(), value)
678 }
679
680 pub fn text_alignment(&self) -> TextAlignment {
682 self.get_value(Self::text_alignment_property())
683 }
684
685 pub fn set_text_alignment(&self, value: TextAlignment) {
686 self.set_value(Self::text_alignment_property(), value)
687 }
688
689 pub fn text_layout(&self) -> Rc<TextLayout> {
691 if let Some(text_layout) = self.text_layout.borrow().as_ref() {
692 return text_layout.clone();
693 }
694
695 let text_layout = self.create_text_layout();
696 *self.text_layout.borrow_mut() = Some(text_layout.clone());
697
698 self.caret_bounds_dirty.set(true);
703
704 text_layout
705 }
706
707 pub fn caret_index(&self) -> i32 {
708 self.get_value(Self::caret_index_property())
709 }
710
711 pub fn set_caret_index(&self, value: i32) {
712 self.set_value(Self::caret_index_property(), value)
713 }
714
715 pub fn cursor_rectangle(&self) -> Rect {
720 self.ensure_caret_bounds();
721
722 self.caret_bounds.get()
723 }
724
725 pub fn password_char(&self) -> char {
728 self.get_value(Self::password_char_property())
729 }
730
731 pub fn set_password_char(&self, value: char) {
732 self.set_value(Self::password_char_property(), value)
733 }
734
735 pub fn reveal_password(&self) -> bool {
736 self.get_value(Self::reveal_password_property())
737 }
738
739 pub fn set_reveal_password(&self, value: bool) {
740 self.set_value(Self::reveal_password_property(), value)
741 }
742
743 pub fn selection_brush(&self) -> Option<Rc<dyn IBrush>> {
744 self.get_value(Self::selection_brush_property())
745 }
746
747 pub fn set_selection_brush(&self, value: Option<Rc<dyn IBrush>>) {
748 self.set_value(Self::selection_brush_property(), value)
749 }
750
751 pub fn selection_foreground_brush(&self) -> Option<Rc<dyn IBrush>> {
752 self.get_value(Self::selection_foreground_brush_property())
753 }
754
755 pub fn set_selection_foreground_brush(&self, value: Option<Rc<dyn IBrush>>) {
756 self.set_value(Self::selection_foreground_brush_property(), value)
757 }
758
759 pub fn caret_brush(&self) -> Option<Rc<dyn IBrush>> {
760 self.get_value(Self::caret_brush_property())
761 }
762
763 pub fn set_caret_brush(&self, value: Option<Rc<dyn IBrush>>) {
764 self.set_value(Self::caret_brush_property(), value)
765 }
766
767 pub fn caret_blink_interval(&self) -> TimeSpan {
769 self.get_value(Self::caret_blink_interval_property())
770 }
771
772 pub fn set_caret_blink_interval(&self, value: TimeSpan) {
773 self.set_value(Self::caret_blink_interval_property(), value)
774 }
775
776 pub fn selection_start(&self) -> i32 {
777 self.get_value(Self::selection_start_property())
778 }
779
780 pub fn set_selection_start(&self, value: i32) {
781 self.set_value(Self::selection_start_property(), value)
782 }
783
784 pub fn selection_end(&self) -> i32 {
785 self.get_value(Self::selection_end_property())
786 }
787
788 pub fn set_selection_end(&self, value: i32) {
789 self.set_value(Self::selection_end_property(), value)
790 }
791
792 pub(crate) fn text_selection_handle_canvas(&self) -> Option<Ref<TextSelectionHandleCanvas>> {
796 self.text_selection_handle_canvas.borrow().clone()
797 }
798
799 pub fn current_im_client(&self) -> Option<Rc<TextBoxTextInputMethodClient>> {
803 self.current_im_client.borrow().as_ref().and_then(Weak::upgrade)
804 }
805
806 pub fn set_current_im_client(&self, value: Option<Rc<TextBoxTextInputMethodClient>>) {
807 *self.current_im_client.borrow_mut() = value.as_ref().map(Rc::downgrade);
808 }
809
810
811 fn utf16_text(&self) -> Option<ReadOnlyMemory<u16>> {
813 if let Some(utf16_text) = self.utf16_text.borrow().as_ref() {
814 return utf16_text.clone();
815 }
816
817 let utf16_text = self.text().map(|text| ReadOnlyMemory::<u16>::from_str(&text));
818 *self.utf16_text.borrow_mut() = Some(utf16_text.clone());
819 utf16_text
820 }
821
822 fn create_text_layout_internal(
827 &self,
828 constraint: Size,
829 text: Option<ReadOnlyMemory<u16>>,
830 typeface: Typeface,
831 text_style_overrides: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>>,
832 ) -> Rc<TextLayout> {
833 let max_width = if MathUtilities::is_zero(constraint.width) { f64::INFINITY } else { constraint.width };
834 let max_height = if MathUtilities::is_zero(constraint.height) { f64::INFINITY } else { constraint.height };
835
836 let text_run_cache = self.text_run_cache.borrow_mut().get_or_insert_with(|| Rc::new(TextRunCache::new())).clone();
837
838 Rc::new(TextLayout::from_utf16(
839 text.unwrap_or_default(),
840 typeface,
841 TextLayoutOptions {
842 font_size: self.font_size(),
843 foreground: self.foreground(),
844 text_alignment: self.text_alignment(),
845 text_wrapping: self.text_wrapping(),
846 text_trimming: None,
847 text_decorations: None,
848 flow_direction: self.flow_direction(),
849 max_width,
850 max_height,
851 line_height: self.line_height(),
852 letter_spacing: self.letter_spacing(),
853 max_lines: 0,
854 font_features: self.font_features(),
855 text_style_overrides,
856 text_run_cache: Some(text_run_cache),
857 },
858 ))
859 }
860
861 fn render_internal(&self, context: &mut DrawingContext) {
863 if let Some(background) = self.background() {
864 context.fill_rectangle(&background, Rect::from_size(self.bounds().size()), 0.0);
865 }
866
867 let mut top = 0.0;
868 let left = 0.0;
869
870 let text_layout = self.text_layout();
871 let text_height = text_layout.height();
872 let bounds_height = self.bounds().height;
873
874 if bounds_height < text_height {
875 match self.vertical_alignment() {
876 VerticalAlignment::Center => top += (bounds_height - text_height) / 2.0,
877 VerticalAlignment::Bottom => top += bounds_height - text_height,
878 _ => {}
879 }
880 }
881
882 text_layout.draw(context, Point::new(left, top));
883 }
884
885 pub(crate) fn get_caret_points(&self) -> (Point, Point) {
887 let last_character_hit = self.last_character_hit.get();
888 let caret_index = last_character_hit.first_character_index() + last_character_hit.trailing_length();
889 let text_layout = self.text_layout();
890 let line_index =
891 text_layout.get_line_index_from_character_index(caret_index, last_character_hit.trailing_length() > 0);
892 let text_line = &text_layout.text_lines()[line_index as usize];
893
894 let caret_bounds = self.caret_bounds.get();
895
896 let mut x = caret_bounds.x.floor() + 0.5;
897 let y = caret_bounds.y.floor() + 0.5;
898 let b = caret_bounds.bottom().ceil() - 0.5;
899
900 if caret_bounds.x > 0.0 && caret_bounds.x >= text_line.width_including_trailing_whitespace() {
901 x -= 1.0;
902 }
903
904 (Point::new(x, y), Point::new(x, b))
905 }
906
907 pub fn show_caret(&self) {
908 self.ensure_caret_timer();
909 self.ensure_text_selection_layer();
910 self.caret_blink.set(true);
911 self.start_caret_timer();
912 self.invalidate_visual();
913 }
914
915 pub(crate) fn ensure_text_selection_layer(&self) {
916 let canvas = self.text_selection_handle_canvas();
917 let canvas = match canvas {
918 Some(canvas) => canvas,
919 None => {
920 let canvas = TextSelectionHandleCanvas::new();
921 *self.text_selection_handle_canvas.borrow_mut() = Some(canvas.clone());
922 canvas
923 }
924 };
925 canvas.set_presenter(Some(&self.to_ref()));
926 let layer = TextSelectorLayer::get_text_selector_layer(self);
927 *self.layer.borrow_mut() = layer.as_ref().map(Ref::downgrade);
928 let is_in_layer = |parent: &Option<Ref<Visual>>| match (parent, &layer) {
929 (Some(parent), Some(layer)) => *parent == *layer,
930 (None, None) => true,
931 _ => false,
932 };
933 let parent = canvas.visual_parent();
934 if parent.is_some() && !is_in_layer(&parent) {
935 if let Some(l) = parent.and_then(|parent| parent.cast::<TextSelectorLayer>()) {
936 l.remove(canvas.clone());
937 }
938 }
939 if let Some(layer) = &layer {
940 if !is_in_layer(&canvas.visual_parent()) {
941 layer.add(canvas);
942 }
943 }
944 }
945
946 pub(crate) fn remove_text_selection_canvas(&self) {
947 let had_layer = self.layer.borrow().is_some();
950 let layer = self.layer.borrow().as_ref().and_then(WeakRef::upgrade);
951 if let (true, Some(canvas)) = (had_layer, self.text_selection_handle_canvas()) {
952 canvas.set_presenter(None);
953 if let Some(layer) = layer {
954 layer.remove(canvas);
955 }
956 }
957
958 *self.text_selection_handle_canvas.borrow_mut() = None;
959 }
960
961 pub fn hide_caret(&self) {
962 self.caret_blink.set(false);
963 self.stop_caret_timer();
964 self.invalidate_visual();
965 }
966
967 pub(crate) fn caret_changed(&self) {
968 if self.visual_parent().is_none() {
969 return;
970 }
971
972 self.ensure_caret_timer();
973
974 let is_enabled = self.caret_timer.borrow().as_ref().is_some_and(|caret_timer| caret_timer.is_enabled());
975
976 if is_enabled {
977 self.caret_blink.set(true);
978 self.stop_caret_timer();
979 self.start_caret_timer();
980 self.invalidate_visual();
981 } else {
982 self.start_caret_timer();
983 self.invalidate_visual();
984 self.stop_caret_timer();
985 }
986
987 if self.is_measure_valid() {
988 self.bring_into_view_rect(self.caret_bounds.get());
989 } else {
990 let weak = self.to_ref().downgrade();
994
995 Dispatcher::ui_thread().post_local(
996 move || {
997 if let Some(this) = weak.upgrade() {
998 this.bring_into_view_rect(this.caret_bounds.get());
999 }
1000 },
1001 DispatcherPriority::from_value(DispatcherPriority::UI_THREAD_RENDER.value() + 1),
1003 );
1004 }
1005 }
1006
1007 fn get_combined_text(
1009 text: Option<ReadOnlyMemory<u16>>,
1010 caret_index: i32,
1011 preedit_text: Option<&ReadOnlyMemory<u16>>,
1012 ) -> Option<ReadOnlyMemory<u16>> {
1013 let Some(preedit_text) = preedit_text.filter(|preedit_text| !preedit_text.is_empty()) else {
1014 return text;
1015 };
1016
1017 let Some(text) = text.filter(|text| !text.is_empty()) else {
1018 return Some(preedit_text.clone());
1019 };
1020
1021 let text = text.span();
1022 let caret_index = caret_index as usize;
1026 let mut combined = Vec::with_capacity(text.len() + preedit_text.len());
1027
1028 combined.extend_from_slice(&text[..caret_index]);
1029 combined.extend_from_slice(preedit_text.span());
1030 combined.extend_from_slice(&text[caret_index..]);
1031
1032 Some(ReadOnlyMemory::from_vec(combined))
1033 }
1034
1035 fn invalidate_text_layout_keep_cache(&self) {
1036 self.dispose_text_layout();
1037
1038 self.invalidate_visual();
1039 self.invalidate_measure();
1040 }
1041
1042 fn dispose_text_layout(&self) {
1043 let text_layout = self.text_layout.borrow_mut().take();
1044
1045 if let Some(text_layout) = text_layout {
1046 text_layout.dispose();
1047 }
1048 }
1049
1050 fn caret_timer_tick(&self) {
1051 self.caret_blink.set(!self.caret_blink.get());
1052
1053 self.invalidate_visual();
1054 }
1055
1056 fn start_caret_timer(&self) {
1057 let caret_timer = self.caret_timer.borrow().clone();
1058
1059 if let Some(caret_timer) = caret_timer {
1060 caret_timer.start();
1061 }
1062 }
1063
1064 fn stop_caret_timer(&self) {
1065 let caret_timer = self.caret_timer.borrow().clone();
1066
1067 if let Some(caret_timer) = caret_timer {
1068 caret_timer.stop();
1069 }
1070 }
1071
1072 fn unsubscribe_caret_timer_tick(&self) {
1073 let subscription = self.caret_timer_tick.borrow_mut().take();
1074
1075 if let Some(subscription) = subscription {
1076 subscription.dispose();
1077 }
1078 }
1079
1080 fn get_caret_character_hit(text_layout: &TextLayout, text_position: i32, trailing_edge: bool) -> CharacterHit {
1085 let line_index = text_layout.get_line_index_from_character_index(text_position, trailing_edge);
1086 let text_line = &text_layout.text_lines()[line_index as usize];
1087
1088 let mut character_hit = text_line.get_previous_caret_character_hit(CharacterHit::new(text_position));
1089
1090 let next_caret_character_hit = text_line.get_next_caret_character_hit(character_hit);
1091
1092 if next_caret_character_hit.first_character_index() <= text_position {
1093 character_hit = next_caret_character_hit;
1094 }
1095
1096 if text_position == character_hit.first_character_index() + character_hit.trailing_length() {
1097 return character_hit;
1098 }
1099
1100 if trailing_edge {
1101 character_hit
1102 } else {
1103 CharacterHit::new(character_hit.first_character_index())
1104 }
1105 }
1106
1107 pub fn move_caret_to_text_position(&self, text_position: i32, trailing_edge: bool) {
1108 let text_layout = self.text_layout();
1109
1110 self.update_caret(Self::get_caret_character_hit(&text_layout, text_position, trailing_edge), true);
1111
1112 self.navigation_position.set(self.caret_bounds.get().position());
1113
1114 self.caret_changed();
1115 }
1116
1117 pub fn move_caret_to_point(&self, point: Point) {
1118 let hit = self.text_layout().hit_test_point(point);
1119
1120 self.update_caret(hit.character_hit(), true);
1121
1122 self.navigation_position.set(self.caret_bounds.get().position());
1123
1124 self.caret_changed();
1125 }
1126
1127 pub fn move_caret_vertical(&self, direction: LogicalDirection) {
1128 let text_layout = self.text_layout();
1129
1130 let mut line_index = text_layout.get_line_index_from_character_index(
1131 self.caret_index(),
1132 self.last_character_hit.get().trailing_length() > 0,
1133 );
1134
1135 if line_index < 0 {
1136 return;
1137 }
1138
1139 let Point { x: current_x, y: mut current_y } = self.navigation_position.get();
1140
1141 if direction == LogicalDirection::Forward {
1142 if line_index + 1 > text_layout.text_lines().len() as i32 - 1 {
1143 return;
1144 }
1145
1146 let text_line = &text_layout.text_lines()[line_index as usize];
1147
1148 current_y += text_line.height();
1149 } else {
1150 if line_index - 1 < 0 {
1151 return;
1152 }
1153
1154 line_index -= 1;
1155
1156 let text_line = &text_layout.text_lines()[line_index as usize];
1157
1158 current_y -= text_line.height();
1159 }
1160
1161 let navigation_position = self.navigation_position.get();
1162
1163 self.move_caret_to_point(Point::new(current_x, current_y));
1164
1165 self.navigation_position.set(navigation_position.with_y(self.caret_bounds.get().y));
1166
1167 self.caret_changed();
1168 }
1169
1170 fn ensure_caret_timer(&self) {
1171 if self.caret_timer.borrow().is_none() {
1172 self.reset_caret_timer();
1173 }
1174 }
1175
1176 fn reset_caret_timer(&self) {
1177 let mut is_enabled = false;
1178
1179 let caret_timer = self.caret_timer.borrow_mut().take();
1180
1181 if let Some(caret_timer) = caret_timer {
1182 self.unsubscribe_caret_timer_tick();
1183
1184 if caret_timer.is_enabled() {
1185 caret_timer.stop();
1186 is_enabled = true;
1187 }
1188 }
1189
1190 let caret_blink_interval = self.caret_blink_interval();
1191
1192 if caret_blink_interval > TimeSpan::ZERO {
1193 let caret_timer = DispatcherTimer::new();
1194 caret_timer.set_interval(caret_blink_interval.to_duration().unwrap_or_default());
1196
1197 let weak = self.to_ref().downgrade();
1199 let subscription = caret_timer.tick(move |_| {
1200 if let Some(this) = weak.upgrade() {
1201 this.caret_timer_tick();
1202 }
1203 });
1204
1205 *self.caret_timer_tick.borrow_mut() = Some(subscription);
1206 *self.caret_timer.borrow_mut() = Some(caret_timer.clone());
1207
1208 if is_enabled {
1209 caret_timer.start();
1210 }
1211 }
1212 }
1213
1214 pub fn get_next_character_hit(&self, direction: LogicalDirection) -> CharacterHit {
1215 let Some(text) = self.utf16_text() else {
1216 return CharacterHit::default();
1217 };
1218 let text_length = text.len() as i32;
1219
1220 let mut character_hit = self.last_character_hit.get();
1221 let mut caret_index = character_hit.first_character_index() + character_hit.trailing_length();
1222
1223 let text_layout = self.text_layout();
1224 let text_lines = text_layout.text_lines();
1225
1226 let mut line_index = text_layout.get_line_index_from_character_index(caret_index, false);
1227
1228 if line_index < 0 {
1229 return CharacterHit::default();
1230 }
1231
1232 if direction == LogicalDirection::Forward {
1233 while line_index < text_lines.len() as i32 {
1234 let text_line = &text_lines[line_index as usize];
1235
1236 character_hit = text_line.get_next_caret_character_hit(character_hit);
1237
1238 caret_index = character_hit.first_character_index() + character_hit.trailing_length();
1239
1240 if text_line.trailing_whitespace_length() > 0
1241 && caret_index == text_line.first_text_source_index() + text_line.length()
1242 {
1243 character_hit = CharacterHit::new(caret_index);
1244 }
1245
1246 if caret_index >= text_length {
1247 character_hit = CharacterHit::new(text_length);
1248
1249 break;
1250 }
1251
1252 if caret_index - text_line.new_line_length() == text_line.first_text_source_index() + text_line.length()
1253 {
1254 break;
1255 }
1256
1257 if caret_index <= self.caret_index() {
1258 line_index += 1;
1259
1260 continue;
1261 }
1262
1263 break;
1264 }
1265 } else {
1266 while line_index >= 0 {
1267 let text_line = &text_lines[line_index as usize];
1268
1269 character_hit = text_line.get_previous_caret_character_hit(character_hit);
1270
1271 caret_index = character_hit.first_character_index() + character_hit.trailing_length();
1272
1273 if caret_index >= self.caret_index() {
1274 line_index -= 1;
1275
1276 continue;
1277 }
1278
1279 break;
1280 }
1281 }
1282
1283 character_hit
1284 }
1285
1286 pub fn move_caret_horizontal(&self, mut direction: LogicalDirection) {
1287 if self.flow_direction() == FlowDirection::RightToLeft {
1288 direction = if direction == LogicalDirection::Forward {
1289 LogicalDirection::Backward
1290 } else {
1291 LogicalDirection::Forward
1292 };
1293 }
1294
1295 let character_hit = self.get_next_character_hit(direction);
1296
1297 self.update_caret(character_hit, true);
1298
1299 self.navigation_position.set(self.caret_bounds.get().position());
1300
1301 self.caret_changed();
1302 }
1303
1304 pub(crate) fn update_caret(&self, character_hit: CharacterHit, notify: bool) {
1305 self.last_character_hit.set(character_hit);
1306 self.pending_caret_text_position.set(None);
1307 self.caret_bounds_dirty.set(true);
1308
1309 self.ensure_caret_bounds();
1310
1311 if notify {
1312 self.set_current_value(
1313 Self::caret_index_property(),
1314 character_hit.first_character_index() + character_hit.trailing_length(),
1315 );
1316 }
1317 }
1318
1319 fn ensure_caret_bounds(&self) {
1324 if !self.caret_bounds_dirty.get() {
1325 return;
1326 }
1327
1328 let Some(text_layout) = self.text_layout.borrow().clone() else {
1329 return;
1330 };
1331
1332 self.caret_bounds_dirty.set(false);
1333
1334 if let Some(pending_position) = self.pending_caret_text_position.take() {
1335 self.last_character_hit.set(Self::get_caret_character_hit(&text_layout, pending_position, false));
1336 }
1337
1338 let character_hit = self.last_character_hit.get();
1339 let caret_index = character_hit.first_character_index() + character_hit.trailing_length();
1340
1341 let line_index =
1342 text_layout.get_line_index_from_character_index(caret_index, character_hit.trailing_length() > 0);
1343 let text_lines = text_layout.text_lines();
1344 let text_line = &text_lines[line_index as usize];
1345 let distance_x = text_line.get_distance_from_character_hit(character_hit);
1346
1347 let mut distance_y = 0.0;
1348
1349 for text_line in &text_lines[..line_index as usize] {
1350 distance_y += text_line.height();
1351 }
1352
1353 let caret_bounds = Rect::new(distance_x, distance_y, 0.0, text_line.height());
1354
1355 if caret_bounds != self.caret_bounds.get() {
1356 self.caret_bounds.set(caret_bounds);
1357
1358 for (_, handler) in self.caret_bounds_changed.snapshot().iter() {
1362 handler();
1363 }
1364 }
1365 }
1366
1367 fn on_preedit_changed(&self, preedit_text: Option<&str>, cursor_position: Option<i32>) {
1368 match preedit_text.filter(|preedit_text| !preedit_text.is_empty()) {
1369 None => {
1370 self.pending_caret_text_position.set(Some(self.caret_index()));
1371 self.caret_bounds_dirty.set(true);
1372
1373 self.ensure_caret_bounds();
1374 }
1375 Some(preedit_text) => {
1376 let preedit_length = preedit_text.encode_utf16().count() as i32;
1377
1378 let cursor_pos = match cursor_position {
1379 Some(cursor_position) if cursor_position >= 0 && cursor_position <= preedit_length => {
1380 cursor_position
1381 }
1382 _ => preedit_length,
1383 };
1384
1385 self.pending_caret_text_position.set(Some(self.caret_index() + cursor_pos));
1386 self.caret_bounds_dirty.set(true);
1387
1388 self.invalidate_measure();
1389 self.caret_changed();
1390 }
1391 }
1392 }
1393}