Skip to main content

ferroui_controls/presenters/
text_presenter.rs

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/// The control that displays the text of a text box: the text, the
30/// selection, the pre-edit text of an input method and the caret.
31///
32/// Every text position (`CaretIndex`, `SelectionStart`, `SelectionEnd`, the
33/// positions of [`CharacterHit`]) is a UTF-16 code unit index.
34#[repr(C)]
35pub struct TextPresenter {
36    base: Control,
37    caret_timer: RefCell<Option<Rc<DispatcherTimer>>>,
38    /// The subscription of `caret_timer_tick` to the tick of `caret_timer`.
39    caret_timer_tick: RefCell<Option<Rc<dyn IDisposable>>>,
40    caret_blink: Cell<bool>,
41    // Dropped by `invalidate_text_layout` and `invalidate_text_layout_keep_cache`
42    // when a property that affects the text changes, by `measure_override`
43    // against a new constraint, and by `arrange_override` when the final
44    // width differs from the measured one.
45    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    /// The text selector layer the handle canvas was last added to (a layer
56    /// of the top level: referred to weakly).
57    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    /// The UTF-16 form of `Text` (the outer `None`: not transcoded since the
62    /// text changed; the inner one: the text is null).
63    utf16_text: RefCell<Option<Option<ReadOnlyMemory<u16>>>>,
64}
65
66ferro_class! {
67    TextPresenter: Control, virtuals TextPresenterImpl: ControlImpl {
68        /// Creates the `TextLayout` used to render the text.
69        fn create_text_layout(this) -> Rc<TextLayout>;
70        /// Drops the text layout and the shaped runs it was built from.
71        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            // Properties that affect shaping: invalidate the run cache + layout.
107            "PreeditText" | "Foreground" | "FontSize" | "FontStyle" | "FontWeight" | "FontFamily" | "FontStretch"
108            | "Text" | "LetterSpacing" | "PasswordChar" | "RevealPassword" | "FlowDirection"
109            | "SelectionForegroundBrush" | "ShowSelectionHighlight" => {
110                this.invalidate_text_layout();
111            }
112            // Properties that do not affect shaping: preserve the run cache.
113            "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        // After the invalidation above, so the caret is computed against a
127        // layout that already contains the new preedit text; the stale layout
128        // does not cover the preedit-shifted caret index.
129        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    /// Renders the `TextPresenter` to a drawing context.
143    ///
144    /// This member is sealed.
145    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        // The text width used here is matching that TextBlock uses to
239        // measure the text.
240        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        // Check if the constraint has changed since the last measure, if so
257        // recalculate the text layout according to the new size.
258        // NOTE: It is important to check this against the actual final size
259        // (excluding the trailing whitespace) to avoid text layout overflow.
260        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            // Recreate the layout using the arranged width and update the caret
266            // against exactly the same layout that will be rendered.
267            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        /// Defines the `ShowSelectionHighlight` property.
364        pub fn show_selection_highlight_property() -> StyledProperty<bool> {
365            FerroProperty::register::<TextPresenter, _>("ShowSelectionHighlight", true)
366        }
367    );
368
369    ferro_property!(
370        /// Defines the `CaretIndex` property.
371        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        /// Defines the `RevealPassword` property.
379        pub fn reveal_password_property() -> StyledProperty<bool> {
380            FerroProperty::register::<TextPresenter, _>("RevealPassword", false)
381        }
382    );
383
384    ferro_property!(
385        /// Defines the `PasswordChar` property.
386        pub fn password_char_property() -> StyledProperty<char> {
387            FerroProperty::register::<TextPresenter, _>("PasswordChar", '\0')
388        }
389    );
390
391    ferro_property!(
392        /// Defines the `SelectionBrush` property.
393        pub fn selection_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
394            FerroProperty::register::<TextPresenter, _>("SelectionBrush", None)
395        }
396    );
397
398    ferro_property!(
399        /// Defines the `SelectionForegroundBrush` property.
400        pub fn selection_foreground_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
401            FerroProperty::register::<TextPresenter, _>("SelectionForegroundBrush", None)
402        }
403    );
404
405    ferro_property!(
406        /// Defines the `CaretBrush` property.
407        pub fn caret_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
408            FerroProperty::register::<TextPresenter, _>("CaretBrush", None)
409        }
410    );
411
412    ferro_property!(
413        /// Defines the `CaretBlinkInterval` property.
414        pub fn caret_blink_interval_property() -> StyledProperty<TimeSpan> {
415            TextBox::caret_blink_interval_property().add_owner::<TextPresenter>()
416        }
417    );
418
419    ferro_property!(
420        /// Defines the `SelectionStart` property.
421        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        /// Defines the `SelectionEnd` property.
429        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        /// Defines the `Text` property.
437        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        /// Defines the `PreeditText` property.
445        pub fn preedit_text_property() -> StyledProperty<Option<String>> {
446            FerroProperty::register::<TextPresenter, _>("PreeditText", None)
447        }
448    );
449
450    ferro_property!(
451        /// Defines the `PreeditTextCursorPosition` property.
452        pub fn preedit_text_cursor_position_property() -> StyledProperty<Option<i32>> {
453            FerroProperty::register::<TextPresenter, _>("PreeditTextCursorPosition", None)
454        }
455    );
456
457    ferro_property!(
458        /// Defines the `TextAlignment` property.
459        pub fn text_alignment_property() -> AttachedProperty<TextAlignment> {
460            TextBlock::text_alignment_property().add_owner::<TextPresenter>()
461        }
462    );
463
464    ferro_property!(
465        /// Defines the `TextWrapping` property.
466        pub fn text_wrapping_property() -> AttachedProperty<TextWrapping> {
467            TextBlock::text_wrapping_property().add_owner::<TextPresenter>()
468        }
469    );
470
471    ferro_property!(
472        /// Defines the `LineHeight` property.
473        pub fn line_height_property() -> AttachedProperty<f64> {
474            TextBlock::line_height_property().add_owner::<TextPresenter>()
475        }
476    );
477
478    ferro_property!(
479        /// Defines the `LetterSpacing` property.
480        pub fn letter_spacing_property() -> AttachedProperty<f64> {
481            TextElement::letter_spacing_property().add_owner::<TextPresenter>()
482        }
483    );
484
485    ferro_property!(
486        /// Defines the `Background` property.
487        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    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
505    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    /// Initializes a new instance of the `TextPresenter` class.
529    pub fn new() -> Ref<Self> {
530        instantiate(Self::construct())
531    }
532
533    /// Occurs when the bounds of the caret change.
534    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    /// A brush used to paint the control's background.
545    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    /// Whether the presenter shows a selection highlight.
554    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    /// The text.
563    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    /// The text an input method is composing, displayed at the caret.
572    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    /// The position of the cursor within the pre-edit text.
581    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    /// The font family.
590    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    /// The font features.
599    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    /// The font size.
608    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    /// The font style.
617    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    /// The font weight.
626    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    /// The font stretch.
635    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    /// A brush used to paint the text.
644    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    /// The control's text wrapping mode.
653    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    /// The line height. By default, this is set to NaN, which determines the
662    /// appropriate height automatically.
663    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    /// The letter spacing.
672    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    /// The text alignment.
681    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    /// The `TextLayout` used to render the text.
690    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        // The caret is measured against the new layout by
699        // `ensure_caret_bounds`, which the measure pass runs once the layout
700        // is stored. Doing it here would let a `caret_bounds_changed` handler
701        // invalidate the layout this getter is returning.
702        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    /// The caret rectangle relative to this presenter.
716    ///
717    /// The rectangle has zero width. Its X/Y position and height describe the
718    /// caret location within the presenter's text layout coordinates.
719    pub fn cursor_rectangle(&self) -> Rect {
720        self.ensure_caret_bounds();
721
722        self.caret_bounds.get()
723    }
724
725    /// The character displayed in place of every character of the text
726    /// (`'\0'`: none).
727    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    /// The caret blink rate.
768    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    /// The canvas of the touch selection handles of the presenter, once
793    /// they have been asked for. It lives in the text selector layer of the
794    /// top level.
795    pub(crate) fn text_selection_handle_canvas(&self) -> Option<Ref<TextSelectionHandleCanvas>> {
796        self.text_selection_handle_canvas.borrow().clone()
797    }
798
799    /// The input method client that currently edits the text of the
800    /// presenter. The client refers to the presenter, so the presenter
801    /// refers to it weakly.
802    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    /// The UTF-16 form of `Text`, transcoded once per text.
812    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    /// Creates the `TextLayout` used to render the text.
823    ///
824    /// * `constraint` — the constraint of the text.
825    /// * `text` — the text to format.
826    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    /// Renders the `TextPresenter` to a drawing context.
862    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    /// The end points of the line the caret is drawn as.
886    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        // The reference holds the layer strongly; here it is weak, so the canvas is released
948        // from the presenter also when the layer is already gone.
949        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            // The measure is currently invalid so there's no point trying to
991            // bring the current char into view until a measure has been
992            // carried out as the scroll viewer extents may not be up-to-date.
993            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                // `AfterRender`, which the dispatcher does not export by name.
1002                DispatcherPriority::from_value(DispatcherPriority::UI_THREAD_RENDER.value() + 1),
1003            );
1004        }
1005    }
1006
1007    /// The text with the pre-edit text inserted at the caret.
1008    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        // A caret outside of the text is a programmer error, as in the
1023        // reference (the property coerces what is set, a text set afterwards
1024        // does not move it).
1025        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    /// Normalizes a text position into a caret-valid [`CharacterHit`] via the
1081    /// line's caret hit walkers, so positions on cluster or run boundaries
1082    /// (the end of a preedit shaped with a fallback font, a surrogate pair)
1083    /// resolve to a hit the layout can measure.
1084    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            // Positive here, so the interval is a duration.
1195            caret_timer.set_interval(caret_blink_interval.to_duration().unwrap_or_default());
1196
1197            // The handler is created once per timer: a tick allocates nothing.
1198            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    /// Measures the caret against the current layout and reports a move.
1320    /// Reads the layout field rather than the property, so it never builds
1321    /// one: with no layout the caret stays dirty until the measure pass that
1322    /// builds it calls back here.
1323    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            // A handler may invalidate the layout from here. Nothing below
1359            // reads it, and the invalidation schedules the measure pass that
1360            // rebuilds it.
1361            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}