Skip to main content

i_slint_core/items/
text.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5This module contains the builtin text related items.
6
7When adding an item or a property, it needs to be kept in sync with different place.
8Lookup the [`crate::items`] module documentation.
9*/
10use super::{
11    EventResult, FontMetrics, InputType, Item, ItemConsts, ItemRc, ItemRef, KeyEventArg,
12    KeyEventResult, KeyEventType, PointArg, PointerEventButton, RenderingResult, StringArg,
13    TextHorizontalAlignment, TextOverflow, TextStrokeStyle, TextVerticalAlignment, TextWrap,
14    VoidArg, WindowItem,
15};
16use crate::graphics::{Brush, Color, FontRequest};
17use crate::input::{
18    FocusEvent, FocusEventResult, FocusReason, InputEventFilterResult, InputEventResult, KeyEvent,
19    KeyboardModifiers, MouseEvent, StandardShortcut, TextShortcut, key_codes,
20};
21use crate::item_rendering::{
22    CachedRenderingData, HasFont, ItemRenderer, PlainOrStyledText, RenderString, RenderText,
23};
24use crate::layout::{LayoutInfo, Orientation};
25use crate::lengths::{LogicalLength, LogicalPoint, LogicalRect, LogicalSize};
26use crate::platform::Clipboard;
27#[cfg(feature = "rtti")]
28use crate::rtti::*;
29use crate::window::{InputMethodProperties, InputMethodRequest, WindowAdapter, WindowInner};
30use crate::{Callback, Coord, Property, SharedString, SharedVector};
31use alloc::rc::Rc;
32use alloc::string::String;
33use const_field_offset::FieldOffsets;
34use core::cell::Cell;
35use core::pin::Pin;
36#[allow(unused)]
37use euclid::num::Ceil;
38use i_slint_core_macros::*;
39use unicode_segmentation::UnicodeSegmentation;
40
41/// The implementation of the `Text` element
42#[repr(C)]
43#[derive(FieldOffsets, Default, SlintElement)]
44#[pin]
45pub struct ComplexText {
46    pub width: Property<LogicalLength>,
47    pub height: Property<LogicalLength>,
48    pub text: Property<SharedString>,
49    pub font_size: Property<LogicalLength>,
50    pub font_weight: Property<i32>,
51    pub color: Property<Brush>,
52    pub horizontal_alignment: Property<TextHorizontalAlignment>,
53    pub vertical_alignment: Property<TextVerticalAlignment>,
54
55    pub font_family: Property<SharedString>,
56    pub font_italic: Property<bool>,
57    pub wrap: Property<TextWrap>,
58    pub overflow: Property<TextOverflow>,
59    pub letter_spacing: Property<LogicalLength>,
60    pub stroke: Property<Brush>,
61    pub stroke_width: Property<LogicalLength>,
62    pub stroke_style: Property<TextStrokeStyle>,
63    pub cached_rendering_data: CachedRenderingData,
64}
65
66impl Item for ComplexText {
67    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
68
69    fn layout_info(
70        self: Pin<&Self>,
71        orientation: Orientation,
72        window_adapter: &Rc<dyn WindowAdapter>,
73        self_rc: &ItemRc,
74    ) -> LayoutInfo {
75        text_layout_info(
76            self,
77            &self_rc,
78            window_adapter,
79            orientation,
80            Self::FIELD_OFFSETS.width.apply_pin(self),
81        )
82    }
83
84    fn input_event_filter_before_children(
85        self: Pin<&Self>,
86        _: &MouseEvent,
87        _window_adapter: &Rc<dyn WindowAdapter>,
88        _self_rc: &ItemRc,
89    ) -> InputEventFilterResult {
90        InputEventFilterResult::ForwardAndIgnore
91    }
92
93    fn input_event(
94        self: Pin<&Self>,
95        _: &MouseEvent,
96        _window_adapter: &Rc<dyn WindowAdapter>,
97        _self_rc: &ItemRc,
98    ) -> InputEventResult {
99        InputEventResult::EventIgnored
100    }
101
102    fn capture_key_event(
103        self: Pin<&Self>,
104        _: &KeyEvent,
105        _window_adapter: &Rc<dyn WindowAdapter>,
106        _self_rc: &ItemRc,
107    ) -> KeyEventResult {
108        KeyEventResult::EventIgnored
109    }
110
111    fn key_event(
112        self: Pin<&Self>,
113        _: &KeyEvent,
114        _window_adapter: &Rc<dyn WindowAdapter>,
115        _self_rc: &ItemRc,
116    ) -> KeyEventResult {
117        KeyEventResult::EventIgnored
118    }
119
120    fn focus_event(
121        self: Pin<&Self>,
122        _: &FocusEvent,
123        _window_adapter: &Rc<dyn WindowAdapter>,
124        _self_rc: &ItemRc,
125    ) -> FocusEventResult {
126        FocusEventResult::FocusIgnored
127    }
128
129    fn render(
130        self: Pin<&Self>,
131        backend: &mut &mut dyn ItemRenderer,
132        self_rc: &ItemRc,
133        size: LogicalSize,
134    ) -> RenderingResult {
135        (*backend).draw_text(self, self_rc, size, &self.cached_rendering_data);
136        RenderingResult::ContinueRenderingChildren
137    }
138
139    fn bounding_rect(
140        self: core::pin::Pin<&Self>,
141        _window_adapter: &Rc<dyn WindowAdapter>,
142        _self_rc: &ItemRc,
143        geometry: LogicalRect,
144    ) -> LogicalRect {
145        geometry
146    }
147
148    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
149        false
150    }
151}
152
153impl ItemConsts for ComplexText {
154    const cached_rendering_data_offset: const_field_offset::FieldOffset<
155        ComplexText,
156        CachedRenderingData,
157    > = ComplexText::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
158}
159
160impl HasFont for ComplexText {
161    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest {
162        crate::items::WindowItem::resolved_font_request(
163            self_rc,
164            self.font_family(),
165            self.font_weight(),
166            self.font_size(),
167            self.letter_spacing(),
168            self.font_italic(),
169        )
170    }
171}
172
173impl RenderString for ComplexText {
174    fn text(self: Pin<&Self>) -> PlainOrStyledText {
175        PlainOrStyledText::Plain(self.text())
176    }
177}
178
179impl RenderText for ComplexText {
180    fn target_size(self: Pin<&Self>) -> LogicalSize {
181        LogicalSize::from_lengths(self.width(), self.height())
182    }
183
184    fn color(self: Pin<&Self>) -> Brush {
185        self.color()
186    }
187
188    fn link_color(self: Pin<&Self>) -> Color {
189        Default::default()
190    }
191
192    fn alignment(
193        self: Pin<&Self>,
194    ) -> (super::TextHorizontalAlignment, super::TextVerticalAlignment) {
195        (self.horizontal_alignment(), self.vertical_alignment())
196    }
197
198    fn wrap(self: Pin<&Self>) -> TextWrap {
199        self.wrap()
200    }
201
202    fn overflow(self: Pin<&Self>) -> TextOverflow {
203        self.overflow()
204    }
205
206    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
207        (self.stroke(), self.stroke_width(), self.stroke_style())
208    }
209
210    fn is_markdown(self: Pin<&Self>) -> bool {
211        false
212    }
213}
214
215impl ComplexText {
216    pub fn font_metrics(
217        self: Pin<&Self>,
218        window_adapter: &Rc<dyn WindowAdapter>,
219        self_rc: &ItemRc,
220    ) -> FontMetrics {
221        let font_request = self.font_request(self_rc);
222        window_adapter.renderer().font_metrics(font_request)
223    }
224}
225
226/// The implementation of the `Text` element
227#[repr(C)]
228#[derive(FieldOffsets, Default, SlintElement)]
229#[pin]
230pub struct StyledTextItem {
231    pub width: Property<LogicalLength>,
232    pub height: Property<LogicalLength>,
233    pub text: Property<crate::styled_text::StyledText>,
234    pub font_size: Property<LogicalLength>,
235    pub font_weight: Property<i32>,
236    pub color: Property<Brush>,
237    pub horizontal_alignment: Property<TextHorizontalAlignment>,
238    pub vertical_alignment: Property<TextVerticalAlignment>,
239    pub link_clicked: Callback<StringArg>,
240
241    pub font_family: Property<SharedString>,
242    pub font_italic: Property<bool>,
243    pub wrap: Property<TextWrap>,
244    pub overflow: Property<TextOverflow>,
245    pub letter_spacing: Property<LogicalLength>,
246    pub stroke: Property<Brush>,
247    pub stroke_width: Property<LogicalLength>,
248    pub stroke_style: Property<TextStrokeStyle>,
249    pub link_color: Property<Color>,
250    pub cached_rendering_data: CachedRenderingData,
251}
252
253impl Item for StyledTextItem {
254    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
255
256    fn layout_info(
257        self: Pin<&Self>,
258        orientation: Orientation,
259        window_adapter: &Rc<dyn WindowAdapter>,
260        self_rc: &ItemRc,
261    ) -> LayoutInfo {
262        text_layout_info(
263            self,
264            &self_rc,
265            window_adapter,
266            orientation,
267            Self::FIELD_OFFSETS.width.apply_pin(self),
268        )
269    }
270
271    fn input_event_filter_before_children(
272        self: Pin<&Self>,
273        _: &MouseEvent,
274        _window_adapter: &Rc<dyn WindowAdapter>,
275        _self_rc: &ItemRc,
276    ) -> InputEventFilterResult {
277        InputEventFilterResult::ForwardEvent
278    }
279
280    #[cfg(feature = "experimental-rich-text")]
281    fn input_event(
282        self: Pin<&Self>,
283        event: &MouseEvent,
284        window_adapter: &Rc<dyn WindowAdapter>,
285        self_rc: &ItemRc,
286    ) -> InputEventResult {
287        match event {
288            MouseEvent::Pressed {
289                position,
290                button: PointerEventButton::Left,
291                click_count: _,
292                is_touch: _,
293            } => {
294                let window_inner = WindowInner::from_pub(window_adapter.window());
295                let scale_factor = crate::lengths::ScaleFactor::new(window_inner.scale_factor());
296                if let Some(link) = crate::textlayout::sharedparley::link_under_cursor(
297                    scale_factor,
298                    self,
299                    self_rc,
300                    LogicalSize::from_lengths(self.width(), self.height()),
301                    *position * scale_factor,
302                ) {
303                    Self::FIELD_OFFSETS.link_clicked.apply_pin(self).call(&(link.into(),));
304                }
305
306                InputEventResult::EventAccepted
307            }
308            _ => InputEventResult::EventIgnored,
309        }
310    }
311
312    #[cfg(not(feature = "experimental-rich-text"))]
313    fn input_event(
314        self: Pin<&Self>,
315        _: &MouseEvent,
316        _window_adapter: &Rc<dyn WindowAdapter>,
317        _self_rc: &ItemRc,
318    ) -> InputEventResult {
319        InputEventResult::EventIgnored
320    }
321
322    fn capture_key_event(
323        self: Pin<&Self>,
324        _: &KeyEvent,
325        _window_adapter: &Rc<dyn WindowAdapter>,
326        _self_rc: &ItemRc,
327    ) -> KeyEventResult {
328        KeyEventResult::EventIgnored
329    }
330
331    fn key_event(
332        self: Pin<&Self>,
333        _: &KeyEvent,
334        _window_adapter: &Rc<dyn WindowAdapter>,
335        _self_rc: &ItemRc,
336    ) -> KeyEventResult {
337        KeyEventResult::EventIgnored
338    }
339
340    fn focus_event(
341        self: Pin<&Self>,
342        _: &FocusEvent,
343        _window_adapter: &Rc<dyn WindowAdapter>,
344        _self_rc: &ItemRc,
345    ) -> FocusEventResult {
346        FocusEventResult::FocusIgnored
347    }
348
349    fn render(
350        self: Pin<&Self>,
351        backend: &mut &mut dyn ItemRenderer,
352        self_rc: &ItemRc,
353        size: LogicalSize,
354    ) -> RenderingResult {
355        (*backend).draw_text(self, self_rc, size, &self.cached_rendering_data);
356        RenderingResult::ContinueRenderingChildren
357    }
358
359    fn bounding_rect(
360        self: core::pin::Pin<&Self>,
361        _window_adapter: &Rc<dyn WindowAdapter>,
362        _self_rc: &ItemRc,
363        geometry: LogicalRect,
364    ) -> LogicalRect {
365        geometry
366    }
367
368    fn clips_children(self: Pin<&Self>) -> bool {
369        false
370    }
371}
372
373impl ItemConsts for StyledTextItem {
374    const cached_rendering_data_offset: const_field_offset::FieldOffset<
375        StyledTextItem,
376        CachedRenderingData,
377    > = StyledTextItem::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
378}
379
380impl HasFont for StyledTextItem {
381    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest {
382        crate::items::WindowItem::resolved_font_request(
383            self_rc,
384            self.font_family(),
385            self.font_weight(),
386            self.font_size(),
387            self.letter_spacing(),
388            self.font_italic(),
389        )
390    }
391}
392
393impl RenderString for StyledTextItem {
394    fn text(self: Pin<&Self>) -> PlainOrStyledText {
395        PlainOrStyledText::Styled(self.text())
396    }
397}
398
399impl RenderText for StyledTextItem {
400    fn target_size(self: Pin<&Self>) -> LogicalSize {
401        LogicalSize::from_lengths(self.width(), self.height())
402    }
403
404    fn color(self: Pin<&Self>) -> Brush {
405        self.color()
406    }
407
408    fn link_color(self: Pin<&Self>) -> Color {
409        self.link_color()
410    }
411
412    fn alignment(
413        self: Pin<&Self>,
414    ) -> (super::TextHorizontalAlignment, super::TextVerticalAlignment) {
415        (self.horizontal_alignment(), self.vertical_alignment())
416    }
417
418    fn wrap(self: Pin<&Self>) -> TextWrap {
419        self.wrap()
420    }
421
422    fn overflow(self: Pin<&Self>) -> TextOverflow {
423        self.overflow()
424    }
425
426    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
427        (self.stroke(), self.stroke_width(), self.stroke_style())
428    }
429
430    fn is_markdown(self: Pin<&Self>) -> bool {
431        true
432    }
433}
434
435impl StyledTextItem {
436    pub fn font_metrics(
437        self: Pin<&Self>,
438        window_adapter: &Rc<dyn WindowAdapter>,
439        self_rc: &ItemRc,
440    ) -> FontMetrics {
441        let font_request = self.font_request(self_rc);
442        window_adapter.renderer().font_metrics(font_request)
443    }
444}
445
446/// The implementation of the `Text` element
447#[repr(C)]
448#[derive(FieldOffsets, Default, SlintElement)]
449#[pin]
450pub struct SimpleText {
451    pub width: Property<LogicalLength>,
452    pub height: Property<LogicalLength>,
453    pub text: Property<SharedString>,
454    pub font_size: Property<LogicalLength>,
455    pub font_weight: Property<i32>,
456    pub color: Property<Brush>,
457    pub horizontal_alignment: Property<TextHorizontalAlignment>,
458    pub vertical_alignment: Property<TextVerticalAlignment>,
459
460    pub cached_rendering_data: CachedRenderingData,
461}
462
463impl Item for SimpleText {
464    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
465
466    fn layout_info(
467        self: Pin<&Self>,
468        orientation: Orientation,
469        window_adapter: &Rc<dyn WindowAdapter>,
470        self_rc: &ItemRc,
471    ) -> LayoutInfo {
472        text_layout_info(
473            self,
474            self_rc,
475            window_adapter,
476            orientation,
477            Self::FIELD_OFFSETS.width.apply_pin(self),
478        )
479    }
480
481    fn input_event_filter_before_children(
482        self: Pin<&Self>,
483        _: &MouseEvent,
484        _window_adapter: &Rc<dyn WindowAdapter>,
485        _self_rc: &ItemRc,
486    ) -> InputEventFilterResult {
487        InputEventFilterResult::ForwardAndIgnore
488    }
489
490    fn input_event(
491        self: Pin<&Self>,
492        _: &MouseEvent,
493        _window_adapter: &Rc<dyn WindowAdapter>,
494        _self_rc: &ItemRc,
495    ) -> InputEventResult {
496        InputEventResult::EventIgnored
497    }
498
499    fn capture_key_event(
500        self: Pin<&Self>,
501        _: &KeyEvent,
502        _window_adapter: &Rc<dyn WindowAdapter>,
503        _self_rc: &ItemRc,
504    ) -> KeyEventResult {
505        KeyEventResult::EventIgnored
506    }
507
508    fn key_event(
509        self: Pin<&Self>,
510        _: &KeyEvent,
511        _window_adapter: &Rc<dyn WindowAdapter>,
512        _self_rc: &ItemRc,
513    ) -> KeyEventResult {
514        KeyEventResult::EventIgnored
515    }
516
517    fn focus_event(
518        self: Pin<&Self>,
519        _: &FocusEvent,
520        _window_adapter: &Rc<dyn WindowAdapter>,
521        _self_rc: &ItemRc,
522    ) -> FocusEventResult {
523        FocusEventResult::FocusIgnored
524    }
525
526    fn render(
527        self: Pin<&Self>,
528        backend: &mut &mut dyn ItemRenderer,
529        self_rc: &ItemRc,
530        size: LogicalSize,
531    ) -> RenderingResult {
532        (*backend).draw_text(self, self_rc, size, &self.cached_rendering_data);
533        RenderingResult::ContinueRenderingChildren
534    }
535
536    fn bounding_rect(
537        self: core::pin::Pin<&Self>,
538        _window_adapter: &Rc<dyn WindowAdapter>,
539        _self_rc: &ItemRc,
540        geometry: LogicalRect,
541    ) -> LogicalRect {
542        geometry
543    }
544
545    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
546        false
547    }
548}
549
550impl ItemConsts for SimpleText {
551    const cached_rendering_data_offset: const_field_offset::FieldOffset<
552        SimpleText,
553        CachedRenderingData,
554    > = SimpleText::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
555}
556
557impl HasFont for SimpleText {
558    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest {
559        crate::items::WindowItem::resolved_font_request(
560            self_rc,
561            SharedString::default(),
562            self.font_weight(),
563            self.font_size(),
564            LogicalLength::default(),
565            false,
566        )
567    }
568}
569
570impl RenderString for SimpleText {
571    fn text(self: Pin<&Self>) -> PlainOrStyledText {
572        PlainOrStyledText::Plain(self.text())
573    }
574}
575
576impl RenderText for SimpleText {
577    fn target_size(self: Pin<&Self>) -> LogicalSize {
578        LogicalSize::from_lengths(self.width(), self.height())
579    }
580
581    fn color(self: Pin<&Self>) -> Brush {
582        self.color()
583    }
584
585    fn link_color(self: Pin<&Self>) -> Color {
586        Default::default()
587    }
588
589    fn alignment(
590        self: Pin<&Self>,
591    ) -> (super::TextHorizontalAlignment, super::TextVerticalAlignment) {
592        (self.horizontal_alignment(), self.vertical_alignment())
593    }
594
595    fn wrap(self: Pin<&Self>) -> TextWrap {
596        TextWrap::default()
597    }
598
599    fn overflow(self: Pin<&Self>) -> TextOverflow {
600        TextOverflow::default()
601    }
602
603    fn stroke(self: Pin<&Self>) -> (Brush, LogicalLength, TextStrokeStyle) {
604        Default::default()
605    }
606
607    fn is_markdown(self: Pin<&Self>) -> bool {
608        false
609    }
610}
611
612impl SimpleText {
613    pub fn font_metrics(
614        self: Pin<&Self>,
615        window_adapter: &Rc<dyn WindowAdapter>,
616        self_rc: &ItemRc,
617    ) -> FontMetrics {
618        window_adapter.renderer().font_metrics(self.font_request(self_rc))
619    }
620}
621
622fn text_layout_info(
623    text: Pin<&dyn RenderText>,
624    self_rc: &ItemRc,
625    window_adapter: &Rc<dyn WindowAdapter>,
626    orientation: Orientation,
627    width: Pin<&Property<LogicalLength>>,
628) -> LayoutInfo {
629    let implicit_size = |max_width, text_wrap| {
630        window_adapter.renderer().text_size(text, self_rc, max_width, text_wrap)
631    };
632
633    // Stretch uses `round_layout` to explicitly align the top left and bottom right of layout nodes
634    // to pixel boundaries. To avoid rounding down causing the minimum width to become so little that
635    // letters will be cut off, apply the ceiling here.
636    match orientation {
637        Orientation::Horizontal => {
638            let implicit_size = implicit_size(None, TextWrap::NoWrap);
639            let min = match text.overflow() {
640                TextOverflow::Elide => implicit_size
641                    .width
642                    .min(window_adapter.renderer().char_size(text, self_rc, '…').width),
643                TextOverflow::Clip => match text.wrap() {
644                    TextWrap::NoWrap => implicit_size.width,
645                    TextWrap::WordWrap | TextWrap::CharWrap => 0 as Coord,
646                },
647            };
648            LayoutInfo {
649                min: min.ceil(),
650                preferred: implicit_size.width.ceil(),
651                ..LayoutInfo::default()
652            }
653        }
654        Orientation::Vertical => {
655            let h = match text.wrap() {
656                TextWrap::NoWrap => implicit_size(None, TextWrap::NoWrap).height,
657                TextWrap::WordWrap => implicit_size(Some(width.get()), TextWrap::WordWrap).height,
658                TextWrap::CharWrap => implicit_size(Some(width.get()), TextWrap::CharWrap).height,
659            }
660            .ceil();
661            LayoutInfo { min: h, preferred: h, ..LayoutInfo::default() }
662        }
663    }
664}
665
666#[repr(C)]
667#[derive(Default, Clone, Copy, PartialEq)]
668/// Similar as `Option<core::ops::Range<i32>>` but `repr(C)`
669///
670/// This is the selection within a preedit
671struct PreEditSelection {
672    valid: bool,
673    start: i32,
674    end: i32,
675}
676
677impl From<Option<core::ops::Range<i32>>> for PreEditSelection {
678    fn from(value: Option<core::ops::Range<i32>>) -> Self {
679        value.map_or_else(Default::default, |r| Self { valid: true, start: r.start, end: r.end })
680    }
681}
682
683impl PreEditSelection {
684    fn as_option(self) -> Option<core::ops::Range<i32>> {
685        self.valid.then_some(self.start..self.end)
686    }
687}
688
689#[repr(C)]
690#[derive(Clone)]
691enum UndoItemKind {
692    TextInsert,
693    TextRemove,
694}
695
696#[repr(C)]
697#[derive(Clone)]
698struct UndoItem {
699    pos: usize,
700    text: SharedString,
701    cursor: usize,
702    anchor: usize,
703    kind: UndoItemKind,
704}
705
706/// The implementation of the `TextInput` element
707#[repr(C)]
708#[derive(FieldOffsets, Default, SlintElement)]
709#[pin]
710pub struct TextInput {
711    pub text: Property<SharedString>,
712    pub font_family: Property<SharedString>,
713    pub font_size: Property<LogicalLength>,
714    pub font_weight: Property<i32>,
715    pub font_italic: Property<bool>,
716    pub color: Property<Brush>,
717    pub selection_foreground_color: Property<Color>,
718    pub selection_background_color: Property<Color>,
719    pub horizontal_alignment: Property<TextHorizontalAlignment>,
720    pub vertical_alignment: Property<TextVerticalAlignment>,
721    pub wrap: Property<TextWrap>,
722    pub input_type: Property<InputType>,
723    pub letter_spacing: Property<LogicalLength>,
724    pub width: Property<LogicalLength>,
725    pub height: Property<LogicalLength>,
726    pub cursor_position_byte_offset: Property<i32>,
727    pub anchor_position_byte_offset: Property<i32>,
728    pub text_cursor_width: Property<LogicalLength>,
729    pub page_height: Property<LogicalLength>,
730    pub cursor_visible: Property<bool>,
731    pub has_focus: Property<bool>,
732    pub enabled: Property<bool>,
733    pub accepted: Callback<VoidArg>,
734    pub cursor_position_changed: Callback<PointArg>,
735    pub edited: Callback<VoidArg>,
736    pub key_pressed: Callback<KeyEventArg, EventResult>,
737    pub key_released: Callback<KeyEventArg, EventResult>,
738    pub single_line: Property<bool>,
739    pub read_only: Property<bool>,
740    pub preedit_text: Property<SharedString>,
741    /// A selection within the preedit (cursor and anchor)
742    preedit_selection: Property<PreEditSelection>,
743    pub cached_rendering_data: CachedRenderingData,
744    // The x position where the cursor wants to be.
745    // It is not updated when moving up and down even when the line is shorter.
746    preferred_x_pos: Cell<Coord>,
747    /// 0 = not pressed, 1 = single press, 2 = double clicked+press , ...
748    pressed: Cell<u8>,
749    undo_items: Cell<SharedVector<UndoItem>>,
750    redo_items: Cell<SharedVector<UndoItem>>,
751}
752
753impl Item for TextInput {
754    fn init(self: Pin<&Self>, _self_rc: &ItemRc) {}
755
756    fn layout_info(
757        self: Pin<&Self>,
758        orientation: Orientation,
759        window_adapter: &Rc<dyn WindowAdapter>,
760        self_rc: &ItemRc,
761    ) -> LayoutInfo {
762        let implicit_size = |max_width, text_wrap| {
763            window_adapter.renderer().text_size(self, self_rc, max_width, text_wrap)
764        };
765
766        // Stretch uses `round_layout` to explicitly align the top left and bottom right of layout nodes
767        // to pixel boundaries. To avoid rounding down causing the minimum width to become so little that
768        // letters will be cut off, apply the ceiling here.
769        match orientation {
770            Orientation::Horizontal => {
771                let implicit_size = implicit_size(None, TextWrap::NoWrap);
772                let min = match self.wrap() {
773                    TextWrap::NoWrap => implicit_size.width,
774                    TextWrap::WordWrap | TextWrap::CharWrap => 0 as Coord,
775                };
776                LayoutInfo {
777                    min: min.ceil(),
778                    preferred: implicit_size.width.ceil(),
779                    ..LayoutInfo::default()
780                }
781            }
782            Orientation::Vertical => {
783                let h = match self.wrap() {
784                    TextWrap::NoWrap => implicit_size(None, TextWrap::NoWrap).height,
785                    TextWrap::WordWrap => {
786                        implicit_size(Some(self.width()), TextWrap::WordWrap).height
787                    }
788                    TextWrap::CharWrap => {
789                        implicit_size(Some(self.width()), TextWrap::CharWrap).height
790                    }
791                }
792                .ceil();
793                LayoutInfo { min: h, preferred: h, ..LayoutInfo::default() }
794            }
795        }
796    }
797
798    fn input_event_filter_before_children(
799        self: Pin<&Self>,
800        _: &MouseEvent,
801        _window_adapter: &Rc<dyn WindowAdapter>,
802        _self_rc: &ItemRc,
803    ) -> InputEventFilterResult {
804        InputEventFilterResult::ForwardEvent
805    }
806
807    fn input_event(
808        self: Pin<&Self>,
809        event: &MouseEvent,
810        window_adapter: &Rc<dyn WindowAdapter>,
811        self_rc: &ItemRc,
812    ) -> InputEventResult {
813        if !self.enabled() {
814            return InputEventResult::EventIgnored;
815        }
816        match event {
817            MouseEvent::Pressed {
818                position, button: PointerEventButton::Left, click_count, ..
819            } => {
820                let clicked_offset =
821                    self.byte_offset_for_position(*position, window_adapter, self_rc) as i32;
822                self.as_ref().pressed.set((click_count % 3) + 1);
823
824                if !window_adapter.window().0.modifiers.get().shift() {
825                    self.as_ref().anchor_position_byte_offset.set(clicked_offset);
826                }
827
828                #[cfg(not(target_os = "android"))]
829                self.ensure_focus_and_ime(window_adapter, self_rc);
830
831                match click_count % 3 {
832                    0 => self.set_cursor_position(
833                        clicked_offset,
834                        true,
835                        TextChangeNotify::TriggerCallbacks,
836                        window_adapter,
837                        self_rc,
838                    ),
839                    1 => self.select_word(window_adapter, self_rc),
840                    2 => self.select_paragraph(window_adapter, self_rc),
841                    _ => unreachable!(),
842                };
843
844                return InputEventResult::GrabMouse;
845            }
846            MouseEvent::Pressed { button: PointerEventButton::Middle, .. } => {
847                #[cfg(not(target_os = "android"))]
848                self.ensure_focus_and_ime(window_adapter, self_rc);
849            }
850            MouseEvent::Released { button: PointerEventButton::Left, .. } => {
851                self.as_ref().pressed.set(0);
852                self.copy_clipboard(window_adapter, Clipboard::SelectionClipboard);
853                #[cfg(target_os = "android")]
854                self.ensure_focus_and_ime(window_adapter, self_rc);
855            }
856            MouseEvent::Released { position, button: PointerEventButton::Middle, .. } => {
857                let clicked_offset =
858                    self.byte_offset_for_position(*position, window_adapter, self_rc) as i32;
859                self.as_ref().anchor_position_byte_offset.set(clicked_offset);
860                self.set_cursor_position(
861                    clicked_offset,
862                    true,
863                    // We trigger the callbacks because paste_clipboard might not if there is no clipboard
864                    TextChangeNotify::TriggerCallbacks,
865                    window_adapter,
866                    self_rc,
867                );
868                self.paste_clipboard(window_adapter, self_rc, Clipboard::SelectionClipboard);
869            }
870            MouseEvent::Exit => {
871                if let Some(x) = window_adapter.internal(crate::InternalToken) {
872                    x.set_mouse_cursor(super::MouseCursor::Default);
873                }
874                self.as_ref().pressed.set(0)
875            }
876            MouseEvent::Moved { position, .. } => {
877                if let Some(x) = window_adapter.internal(crate::InternalToken) {
878                    x.set_mouse_cursor(super::MouseCursor::Text);
879                }
880                let pressed = self.as_ref().pressed.get();
881                if pressed > 0 {
882                    let clicked_offset =
883                        self.byte_offset_for_position(*position, window_adapter, self_rc) as i32;
884                    self.set_cursor_position(
885                        clicked_offset,
886                        true,
887                        if (pressed - 1) % 3 == 0 {
888                            TextChangeNotify::TriggerCallbacks
889                        } else {
890                            TextChangeNotify::SkipCallbacks
891                        },
892                        window_adapter,
893                        self_rc,
894                    );
895                    match (pressed - 1) % 3 {
896                        0 => (),
897                        1 => self.select_word(window_adapter, self_rc),
898                        2 => self.select_paragraph(window_adapter, self_rc),
899                        _ => unreachable!(),
900                    }
901                    return InputEventResult::GrabMouse;
902                }
903            }
904            _ => return InputEventResult::EventIgnored,
905        }
906        InputEventResult::EventAccepted
907    }
908
909    fn capture_key_event(
910        self: Pin<&Self>,
911        _: &KeyEvent,
912        _window_adapter: &Rc<dyn WindowAdapter>,
913        _self_rc: &ItemRc,
914    ) -> KeyEventResult {
915        KeyEventResult::EventIgnored
916    }
917
918    fn key_event(
919        self: Pin<&Self>,
920        event: &KeyEvent,
921        window_adapter: &Rc<dyn WindowAdapter>,
922        self_rc: &ItemRc,
923    ) -> KeyEventResult {
924        if !self.enabled() {
925            return KeyEventResult::EventIgnored;
926        }
927        match event.event_type {
928            KeyEventType::KeyPressed => {
929                // invoke first key_pressed callback to give the developer/designer the possibility to implement a custom behaviour
930                if Self::FIELD_OFFSETS.key_pressed.apply_pin(self).call(&(event.clone(),))
931                    == EventResult::Accept
932                {
933                    return KeyEventResult::EventAccepted;
934                }
935
936                match event.text_shortcut() {
937                    Some(text_shortcut) if !self.read_only() => match text_shortcut {
938                        TextShortcut::Move(direction) => {
939                            TextInput::move_cursor(
940                                self,
941                                direction,
942                                event.modifiers.into(),
943                                TextChangeNotify::TriggerCallbacks,
944                                window_adapter,
945                                self_rc,
946                            );
947                            return KeyEventResult::EventAccepted;
948                        }
949                        TextShortcut::DeleteForward => {
950                            TextInput::select_and_delete(
951                                self,
952                                TextCursorDirection::Forward,
953                                window_adapter,
954                                self_rc,
955                            );
956                            return KeyEventResult::EventAccepted;
957                        }
958                        TextShortcut::DeleteBackward => {
959                            // Special case: backspace breaks the grapheme and selects the previous character
960                            TextInput::select_and_delete(
961                                self,
962                                TextCursorDirection::PreviousCharacter,
963                                window_adapter,
964                                self_rc,
965                            );
966                            return KeyEventResult::EventAccepted;
967                        }
968                        TextShortcut::DeleteWordForward => {
969                            TextInput::select_and_delete(
970                                self,
971                                TextCursorDirection::ForwardByWord,
972                                window_adapter,
973                                self_rc,
974                            );
975                            return KeyEventResult::EventAccepted;
976                        }
977                        TextShortcut::DeleteWordBackward => {
978                            TextInput::select_and_delete(
979                                self,
980                                TextCursorDirection::BackwardByWord,
981                                window_adapter,
982                                self_rc,
983                            );
984                            return KeyEventResult::EventAccepted;
985                        }
986                        TextShortcut::DeleteToStartOfLine => {
987                            TextInput::select_and_delete(
988                                self,
989                                TextCursorDirection::StartOfLine,
990                                window_adapter,
991                                self_rc,
992                            );
993                            return KeyEventResult::EventAccepted;
994                        }
995                    },
996                    Some(_) => {
997                        return KeyEventResult::EventIgnored;
998                    }
999                    None => (),
1000                };
1001
1002                if let Some(keycode) = event.text.chars().next() {
1003                    if keycode == key_codes::Return && !self.read_only() && self.single_line() {
1004                        Self::FIELD_OFFSETS.accepted.apply_pin(self).call(&());
1005                        return KeyEventResult::EventAccepted;
1006                    }
1007                }
1008
1009                // Only insert/interpreter non-control character strings
1010                if event.text.is_empty()
1011                    || event.text.as_str().chars().any(|ch| {
1012                        // exclude the private use area as we encode special keys into it
1013                        ('\u{f700}'..='\u{f7ff}').contains(&ch) || (ch.is_control() && ch != '\n')
1014                    })
1015                {
1016                    return KeyEventResult::EventIgnored;
1017                }
1018
1019                if let Some(shortcut) = event.shortcut() {
1020                    match shortcut {
1021                        StandardShortcut::SelectAll => {
1022                            self.select_all(window_adapter, self_rc);
1023                            return KeyEventResult::EventAccepted;
1024                        }
1025                        StandardShortcut::Copy => {
1026                            self.copy(window_adapter, self_rc);
1027                            return KeyEventResult::EventAccepted;
1028                        }
1029                        StandardShortcut::Paste if !self.read_only() => {
1030                            self.paste(window_adapter, self_rc);
1031                            return KeyEventResult::EventAccepted;
1032                        }
1033                        StandardShortcut::Cut if !self.read_only() => {
1034                            self.cut(window_adapter, self_rc);
1035                            return KeyEventResult::EventAccepted;
1036                        }
1037                        StandardShortcut::Paste | StandardShortcut::Cut => {
1038                            return KeyEventResult::EventIgnored;
1039                        }
1040                        StandardShortcut::Undo if !self.read_only() => {
1041                            self.undo(window_adapter, self_rc);
1042                            return KeyEventResult::EventAccepted;
1043                        }
1044                        StandardShortcut::Redo if !self.read_only() => {
1045                            self.redo(window_adapter, self_rc);
1046                            return KeyEventResult::EventAccepted;
1047                        }
1048                        _ => (),
1049                    }
1050                }
1051
1052                if self.read_only() || event.modifiers.control {
1053                    return KeyEventResult::EventIgnored;
1054                }
1055
1056                // save real anchor/cursor for undo/redo
1057                let (real_cursor, real_anchor) = {
1058                    let text = self.text();
1059                    (self.cursor_position(&text), self.anchor_position(&text))
1060                };
1061
1062                if !self.accept_text_input(event.text.as_str()) {
1063                    return KeyEventResult::EventIgnored;
1064                }
1065
1066                self.delete_selection(window_adapter, self_rc, TextChangeNotify::SkipCallbacks);
1067
1068                let mut text: String = self.text().into();
1069
1070                // FIXME: respect grapheme boundaries
1071                let insert_pos = self.selection_anchor_and_cursor().1;
1072                text.insert_str(insert_pos, &event.text);
1073
1074                self.add_undo_item(UndoItem {
1075                    pos: insert_pos,
1076                    text: event.text.clone(),
1077                    cursor: real_cursor,
1078                    anchor: real_anchor,
1079                    kind: UndoItemKind::TextInsert,
1080                });
1081
1082                self.as_ref().text.set(text.into());
1083                let new_cursor_pos = (insert_pos + event.text.len()) as i32;
1084                self.as_ref().anchor_position_byte_offset.set(new_cursor_pos);
1085                self.set_cursor_position(
1086                    new_cursor_pos,
1087                    true,
1088                    TextChangeNotify::TriggerCallbacks,
1089                    window_adapter,
1090                    self_rc,
1091                );
1092
1093                // Keep the cursor visible when inserting text. Blinking should only occur when
1094                // nothing is entered or the cursor isn't moved.
1095                self.as_ref().show_cursor(window_adapter);
1096
1097                Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
1098
1099                KeyEventResult::EventAccepted
1100            }
1101            KeyEventType::KeyReleased => {
1102                match Self::FIELD_OFFSETS.key_released.apply_pin(self).call(&(event.clone(),)) {
1103                    EventResult::Accept => KeyEventResult::EventAccepted,
1104                    EventResult::Reject => KeyEventResult::EventIgnored,
1105                }
1106            }
1107            KeyEventType::UpdateComposition | KeyEventType::CommitComposition => {
1108                if !self.accept_text_input(&event.text) {
1109                    return KeyEventResult::EventIgnored;
1110                }
1111
1112                let cursor = self.cursor_position(&self.text()) as i32;
1113                self.preedit_text.set(event.preedit_text.clone());
1114                self.preedit_selection.set(event.preedit_selection.clone().into());
1115
1116                if let Some(r) = &event.replacement_range {
1117                    // Set the selection so the call to insert erases it
1118                    self.anchor_position_byte_offset.set(cursor.saturating_add(r.start));
1119                    self.cursor_position_byte_offset.set(cursor.saturating_add(r.end));
1120                    if event.text.is_empty() {
1121                        self.delete_selection(
1122                            window_adapter,
1123                            self_rc,
1124                            if event.cursor_position.is_none() {
1125                                TextChangeNotify::TriggerCallbacks
1126                            } else {
1127                                // will be updated by the set_cursor_position later
1128                                TextChangeNotify::SkipCallbacks
1129                            },
1130                        );
1131                    }
1132                }
1133                self.insert(&event.text, window_adapter, self_rc);
1134                if let Some(cursor) = event.cursor_position {
1135                    self.anchor_position_byte_offset.set(event.anchor_position.unwrap_or(cursor));
1136                    self.set_cursor_position(
1137                        cursor,
1138                        true,
1139                        TextChangeNotify::TriggerCallbacks,
1140                        window_adapter,
1141                        self_rc,
1142                    );
1143                }
1144                KeyEventResult::EventAccepted
1145            }
1146        }
1147    }
1148
1149    fn focus_event(
1150        self: Pin<&Self>,
1151        event: &FocusEvent,
1152        window_adapter: &Rc<dyn WindowAdapter>,
1153        self_rc: &ItemRc,
1154    ) -> FocusEventResult {
1155        match event {
1156            FocusEvent::FocusIn(_reason) => {
1157                if !self.enabled() {
1158                    return FocusEventResult::FocusIgnored;
1159                }
1160                self.has_focus.set(true);
1161                self.show_cursor(window_adapter);
1162                WindowInner::from_pub(window_adapter.window()).set_text_input_focused(true);
1163                // FIXME: This should be tracked by a PropertyTracker in window and toggled when read_only() toggles.
1164                if !self.read_only() {
1165                    if let Some(w) = window_adapter.internal(crate::InternalToken) {
1166                        w.input_method_request(InputMethodRequest::Enable(
1167                            self.ime_properties(window_adapter, self_rc),
1168                        ));
1169                    }
1170
1171                    #[cfg(not(target_vendor = "apple"))]
1172                    if *_reason == FocusReason::TabNavigation {
1173                        self.select_all(window_adapter, self_rc);
1174                    }
1175                }
1176            }
1177            FocusEvent::FocusOut(reason) => {
1178                self.has_focus.set(false);
1179                self.hide_cursor();
1180                if !matches!(reason, FocusReason::WindowActivation | FocusReason::PopupActivation) {
1181                    self.as_ref()
1182                        .anchor_position_byte_offset
1183                        .set(self.as_ref().cursor_position_byte_offset());
1184                }
1185                WindowInner::from_pub(window_adapter.window()).set_text_input_focused(false);
1186                if !self.read_only() {
1187                    if let Some(window_adapter) = window_adapter.internal(crate::InternalToken) {
1188                        window_adapter.input_method_request(InputMethodRequest::Disable);
1189                    }
1190                    // commit the preedit text on android
1191                    #[cfg(target_os = "android")]
1192                    {
1193                        let preedit_text = self.preedit_text();
1194                        if !preedit_text.is_empty() {
1195                            let mut text = String::from(self.text());
1196                            let cursor_position = self.cursor_position(&text);
1197                            text.insert_str(cursor_position, &preedit_text);
1198                            self.text.set(text.into());
1199                            let new_pos = (cursor_position + preedit_text.len()) as i32;
1200                            self.anchor_position_byte_offset.set(new_pos);
1201                            self.set_cursor_position(
1202                                new_pos,
1203                                false,
1204                                TextChangeNotify::TriggerCallbacks,
1205                                window_adapter,
1206                                self_rc,
1207                            );
1208                            Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
1209                        }
1210                    }
1211                    self.preedit_text.set(Default::default());
1212                }
1213            }
1214        }
1215        FocusEventResult::FocusAccepted
1216    }
1217
1218    fn render(
1219        self: Pin<&Self>,
1220        backend: &mut &mut dyn ItemRenderer,
1221        self_rc: &ItemRc,
1222        size: LogicalSize,
1223    ) -> RenderingResult {
1224        crate::properties::evaluate_no_tracking(|| {
1225            if self.has_focus() && self.text() != *backend.window().last_ime_text.borrow() {
1226                let window_adapter = &backend.window().window_adapter();
1227                if let Some(w) = window_adapter.internal(crate::InternalToken) {
1228                    w.input_method_request(InputMethodRequest::Update(
1229                        self.ime_properties(window_adapter, self_rc),
1230                    ));
1231                }
1232            }
1233        });
1234        (*backend).draw_text_input(self, self_rc, size);
1235        RenderingResult::ContinueRenderingChildren
1236    }
1237
1238    fn bounding_rect(
1239        self: core::pin::Pin<&Self>,
1240        _window_adapter: &Rc<dyn WindowAdapter>,
1241        _self_rc: &ItemRc,
1242        geometry: LogicalRect,
1243    ) -> LogicalRect {
1244        geometry
1245    }
1246
1247    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
1248        false
1249    }
1250}
1251
1252impl ItemConsts for TextInput {
1253    const cached_rendering_data_offset: const_field_offset::FieldOffset<
1254        TextInput,
1255        CachedRenderingData,
1256    > = TextInput::FIELD_OFFSETS.cached_rendering_data.as_unpinned_projection();
1257}
1258
1259impl HasFont for TextInput {
1260    fn font_request(self: Pin<&Self>, self_rc: &crate::items::ItemRc) -> FontRequest {
1261        crate::items::WindowItem::resolved_font_request(
1262            self_rc,
1263            self.font_family(),
1264            self.font_weight(),
1265            self.font_size(),
1266            self.letter_spacing(),
1267            self.font_italic(),
1268        )
1269    }
1270}
1271
1272impl RenderString for TextInput {
1273    fn text(self: Pin<&Self>) -> PlainOrStyledText {
1274        PlainOrStyledText::Plain(self.as_ref().text())
1275    }
1276}
1277
1278pub enum TextCursorDirection {
1279    Forward,
1280    Backward,
1281    ForwardByWord,
1282    BackwardByWord,
1283    NextLine,
1284    PreviousLine,
1285    /// breaks grapheme boundaries, so only used by delete-previous-char
1286    PreviousCharacter,
1287    StartOfLine,
1288    EndOfLine,
1289    /// These don't care about wrapping
1290    StartOfParagraph,
1291    EndOfParagraph,
1292    StartOfText,
1293    EndOfText,
1294    PageUp,
1295    PageDown,
1296}
1297
1298impl core::convert::TryFrom<char> for TextCursorDirection {
1299    type Error = ();
1300
1301    fn try_from(value: char) -> Result<Self, Self::Error> {
1302        Ok(match value {
1303            key_codes::LeftArrow => Self::Backward,
1304            key_codes::RightArrow => Self::Forward,
1305            key_codes::UpArrow => Self::PreviousLine,
1306            key_codes::DownArrow => Self::NextLine,
1307            key_codes::PageUp => Self::PageUp,
1308            key_codes::PageDown => Self::PageDown,
1309            // On macos this scrolls to the top or the bottom of the page
1310            #[cfg(not(target_os = "macos"))]
1311            key_codes::Home => Self::StartOfLine,
1312            #[cfg(not(target_os = "macos"))]
1313            key_codes::End => Self::EndOfLine,
1314            _ => return Err(()),
1315        })
1316    }
1317}
1318
1319#[derive(PartialEq)]
1320enum AnchorMode {
1321    KeepAnchor,
1322    MoveAnchor,
1323}
1324
1325impl From<KeyboardModifiers> for AnchorMode {
1326    fn from(modifiers: KeyboardModifiers) -> Self {
1327        if modifiers.shift { Self::KeepAnchor } else { Self::MoveAnchor }
1328    }
1329}
1330
1331/// Argument to [`TextInput::delete_selection`] that determines whether to trigger the
1332/// `edited` and cursor position callbacks and issue an input method request update.
1333#[derive(Copy, Clone, PartialEq, Eq)]
1334pub enum TextChangeNotify {
1335    /// Trigger the callbacks.
1336    TriggerCallbacks,
1337    /// Skip triggering the callbacks, as a subsequent operation will trigger them.
1338    SkipCallbacks,
1339}
1340
1341fn safe_byte_offset(unsafe_byte_offset: i32, text: &str) -> usize {
1342    if unsafe_byte_offset <= 0 {
1343        return 0;
1344    }
1345    let byte_offset_candidate = unsafe_byte_offset as usize;
1346
1347    if byte_offset_candidate >= text.len() {
1348        return text.len();
1349    }
1350
1351    if text.is_char_boundary(byte_offset_candidate) {
1352        return byte_offset_candidate;
1353    }
1354
1355    // Use std::floor_char_boundary once stabilized.
1356    text.char_indices()
1357        .find_map(|(offset, _)| if offset >= byte_offset_candidate { Some(offset) } else { None })
1358        .unwrap_or(text.len())
1359}
1360
1361/// This struct holds the fields needed for rendering a TextInput item after applying any
1362/// on-going composition. This way the renderer's don't have to duplicate the code for extracting
1363/// and applying the pre-edit text, cursor placement within, etc.
1364#[derive(Debug)]
1365pub struct TextInputVisualRepresentation {
1366    /// The text to be rendered including any pre-edit string
1367    pub text: String,
1368    /// If set, this field specifies the range as byte offsets within the text field where the composition
1369    /// is in progress. Renderers typically provide visual feedback for the currently composed text, such as
1370    /// by using underlines.
1371    pub preedit_range: core::ops::Range<usize>,
1372    /// If set, specifies the range as byte offsets within the text where to draw the selection.
1373    pub selection_range: core::ops::Range<usize>,
1374    /// The position where to draw the cursor, as byte offset within the text.
1375    pub cursor_position: Option<usize>,
1376    /// The color of the (unselected) text
1377    pub text_color: Brush,
1378    /// The color of the blinking cursor
1379    pub cursor_color: Color,
1380    text_without_password: Option<String>,
1381    password_character: char,
1382}
1383
1384impl TextInputVisualRepresentation {
1385    /// If the given `TextInput` renders a password, then all characters in this `TextInputVisualRepresentation` are replaced
1386    /// with the password character and the selection/preedit-ranges/cursor position are adjusted.
1387    /// If `password_character_fn` is Some, it is called lazily to query the password character, otherwise a default is used.
1388    fn apply_password_character_substitution(
1389        &mut self,
1390        text_input: Pin<&TextInput>,
1391        password_character_fn: Option<fn() -> char>,
1392    ) {
1393        if !matches!(text_input.input_type(), InputType::Password) {
1394            return;
1395        }
1396
1397        let password_character = password_character_fn.map_or('●', |f| f());
1398
1399        let text = &mut self.text;
1400        let fixup_range = |r: &mut core::ops::Range<usize>| {
1401            if !core::ops::Range::is_empty(r) {
1402                r.start = text[..r.start].chars().count() * password_character.len_utf8();
1403                r.end = text[..r.end].chars().count() * password_character.len_utf8();
1404            }
1405        };
1406        fixup_range(&mut self.preedit_range);
1407        fixup_range(&mut self.selection_range);
1408        if let Some(cursor_pos) = self.cursor_position.as_mut() {
1409            *cursor_pos = text[..*cursor_pos].chars().count() * password_character.len_utf8();
1410        }
1411        self.text_without_password = Some(core::mem::replace(
1412            text,
1413            core::iter::repeat_n(password_character, text.chars().count()).collect(),
1414        ));
1415        self.password_character = password_character;
1416    }
1417
1418    /// Use this function to make a byte offset in the text used for rendering back to a byte offset in the
1419    /// TextInput's text. The offsets might differ for example for password text input fields.
1420    pub fn map_byte_offset_from_byte_offset_in_visual_text(&self, byte_offset: usize) -> usize {
1421        if let Some(text_without_password) = self.text_without_password.as_ref() {
1422            text_without_password
1423                .char_indices()
1424                .nth(byte_offset / self.password_character.len_utf8())
1425                .map_or(text_without_password.len(), |(r, _)| r)
1426        } else {
1427            byte_offset
1428        }
1429    }
1430}
1431
1432impl TextInput {
1433    fn show_cursor(&self, window_adapter: &Rc<dyn WindowAdapter>) {
1434        WindowInner::from_pub(window_adapter.window())
1435            .set_cursor_blink_binding(&self.cursor_visible);
1436    }
1437
1438    fn hide_cursor(&self) {
1439        self.cursor_visible.set(false);
1440    }
1441
1442    /// Moves the cursor (and/or anchor) and returns true if the cursor position changed; false otherwise.
1443    fn move_cursor(
1444        self: Pin<&Self>,
1445        direction: TextCursorDirection,
1446        anchor_mode: AnchorMode,
1447        trigger_callbacks: TextChangeNotify,
1448        window_adapter: &Rc<dyn WindowAdapter>,
1449        self_rc: &ItemRc,
1450    ) -> bool {
1451        let text = self.text();
1452        if text.is_empty() {
1453            return false;
1454        }
1455
1456        let (anchor, cursor) = self.selection_anchor_and_cursor();
1457        let last_cursor_pos = self.cursor_position(&text);
1458
1459        let mut grapheme_cursor =
1460            unicode_segmentation::GraphemeCursor::new(last_cursor_pos, text.len(), true);
1461
1462        let font_height = window_adapter.renderer().char_size(self, self_rc, ' ').height;
1463
1464        let mut reset_preferred_x_pos = true;
1465
1466        let new_cursor_pos = match direction {
1467            TextCursorDirection::Forward => {
1468                if anchor == cursor || anchor_mode == AnchorMode::KeepAnchor {
1469                    grapheme_cursor
1470                        .next_boundary(&text, 0)
1471                        .ok()
1472                        .flatten()
1473                        .unwrap_or_else(|| text.len())
1474                } else {
1475                    cursor
1476                }
1477            }
1478            TextCursorDirection::Backward => {
1479                if anchor == cursor || anchor_mode == AnchorMode::KeepAnchor {
1480                    grapheme_cursor.prev_boundary(&text, 0).ok().flatten().unwrap_or(0)
1481                } else {
1482                    anchor
1483                }
1484            }
1485            TextCursorDirection::NextLine => {
1486                reset_preferred_x_pos = false;
1487
1488                let cursor_rect =
1489                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1490                let mut cursor_xy_pos = cursor_rect.center();
1491
1492                cursor_xy_pos.y += font_height;
1493                cursor_xy_pos.x = self.preferred_x_pos.get();
1494                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1495            }
1496            TextCursorDirection::PreviousLine => {
1497                reset_preferred_x_pos = false;
1498
1499                let cursor_rect =
1500                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1501                let mut cursor_xy_pos = cursor_rect.center();
1502
1503                cursor_xy_pos.y -= font_height;
1504                cursor_xy_pos.x = self.preferred_x_pos.get();
1505                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1506            }
1507            TextCursorDirection::PreviousCharacter => {
1508                let mut i = last_cursor_pos;
1509                loop {
1510                    i = i.checked_sub(1).unwrap_or_default();
1511                    if text.is_char_boundary(i) {
1512                        break i;
1513                    }
1514                }
1515            }
1516            // Currently moving by word behaves like macos: next end of word(forward) or previous beginning of word(backward)
1517            TextCursorDirection::ForwardByWord => next_word_boundary(&text, last_cursor_pos + 1),
1518            TextCursorDirection::BackwardByWord => {
1519                prev_word_boundary(&text, last_cursor_pos.saturating_sub(1))
1520            }
1521            TextCursorDirection::StartOfLine => {
1522                let cursor_rect =
1523                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1524                let mut cursor_xy_pos = cursor_rect.center();
1525
1526                cursor_xy_pos.x = 0 as Coord;
1527                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1528            }
1529            TextCursorDirection::EndOfLine => {
1530                let cursor_rect =
1531                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1532                let mut cursor_xy_pos = cursor_rect.center();
1533
1534                cursor_xy_pos.x = Coord::MAX;
1535                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1536            }
1537            TextCursorDirection::StartOfParagraph => {
1538                prev_paragraph_boundary(&text, last_cursor_pos.saturating_sub(1))
1539            }
1540            TextCursorDirection::EndOfParagraph => {
1541                next_paragraph_boundary(&text, last_cursor_pos + 1)
1542            }
1543            TextCursorDirection::StartOfText => 0,
1544            TextCursorDirection::EndOfText => text.len(),
1545            TextCursorDirection::PageUp => {
1546                let offset = self.page_height().get() - font_height;
1547                if offset <= 0 as Coord {
1548                    return false;
1549                }
1550                reset_preferred_x_pos = false;
1551                let cursor_rect =
1552                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1553                let mut cursor_xy_pos = cursor_rect.center();
1554                cursor_xy_pos.y -= offset;
1555                cursor_xy_pos.x = self.preferred_x_pos.get();
1556                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1557            }
1558            TextCursorDirection::PageDown => {
1559                let offset = self.page_height().get() - font_height;
1560                if offset <= 0 as Coord {
1561                    return false;
1562                }
1563                reset_preferred_x_pos = false;
1564                let cursor_rect =
1565                    self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1566                let mut cursor_xy_pos = cursor_rect.center();
1567                cursor_xy_pos.y += offset;
1568                cursor_xy_pos.x = self.preferred_x_pos.get();
1569                self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1570            }
1571        };
1572
1573        match anchor_mode {
1574            AnchorMode::KeepAnchor => {}
1575            AnchorMode::MoveAnchor => {
1576                self.as_ref().anchor_position_byte_offset.set(new_cursor_pos as i32);
1577            }
1578        }
1579        self.set_cursor_position(
1580            new_cursor_pos as i32,
1581            reset_preferred_x_pos,
1582            trigger_callbacks,
1583            window_adapter,
1584            self_rc,
1585        );
1586
1587        // Keep the cursor visible when moving. Blinking should only occur when
1588        // nothing is entered or the cursor isn't moved.
1589        self.as_ref().show_cursor(window_adapter);
1590
1591        new_cursor_pos != last_cursor_pos
1592    }
1593
1594    pub fn set_cursor_position(
1595        self: Pin<&Self>,
1596        new_position: i32,
1597        reset_preferred_x_pos: bool,
1598        trigger_callbacks: TextChangeNotify,
1599        window_adapter: &Rc<dyn WindowAdapter>,
1600        self_rc: &ItemRc,
1601    ) {
1602        self.cursor_position_byte_offset.set(new_position);
1603        if new_position >= 0 {
1604            let pos = self
1605                .cursor_rect_for_byte_offset(new_position as usize, window_adapter, self_rc)
1606                .origin;
1607            if reset_preferred_x_pos {
1608                self.preferred_x_pos.set(pos.x);
1609            }
1610            if trigger_callbacks == TextChangeNotify::TriggerCallbacks {
1611                Self::FIELD_OFFSETS
1612                    .cursor_position_changed
1613                    .apply_pin(self)
1614                    .call(&(crate::api::LogicalPosition::from_euclid(pos),));
1615                self.update_ime(window_adapter, self_rc);
1616            }
1617        }
1618    }
1619
1620    fn update_ime(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1621        if self.read_only() || !self.has_focus() {
1622            return;
1623        }
1624        if let Some(w) = window_adapter.internal(crate::InternalToken) {
1625            w.input_method_request(InputMethodRequest::Update(
1626                self.ime_properties(window_adapter, self_rc),
1627            ));
1628        }
1629    }
1630
1631    fn select_and_delete(
1632        self: Pin<&Self>,
1633        step: TextCursorDirection,
1634        window_adapter: &Rc<dyn WindowAdapter>,
1635        self_rc: &ItemRc,
1636    ) {
1637        if !self.has_selection() {
1638            self.move_cursor(
1639                step,
1640                AnchorMode::KeepAnchor,
1641                TextChangeNotify::SkipCallbacks,
1642                window_adapter,
1643                self_rc,
1644            );
1645        }
1646        self.delete_selection(window_adapter, self_rc, TextChangeNotify::TriggerCallbacks);
1647    }
1648
1649    pub fn delete_selection(
1650        self: Pin<&Self>,
1651        window_adapter: &Rc<dyn WindowAdapter>,
1652        self_rc: &ItemRc,
1653        trigger_callbacks: TextChangeNotify,
1654    ) {
1655        let text: String = self.text().into();
1656        if text.is_empty() {
1657            return;
1658        }
1659
1660        let (anchor, cursor) = self.selection_anchor_and_cursor();
1661        if anchor == cursor {
1662            return;
1663        }
1664
1665        let removed_text: SharedString = text[anchor..cursor].into();
1666        // save real anchor/cursor for undo/redo
1667        let (real_cursor, real_anchor) = {
1668            let text = self.text();
1669            (self.cursor_position(&text), self.anchor_position(&text))
1670        };
1671
1672        let text = [text.split_at(anchor).0, text.split_at(cursor).1].concat();
1673        self.text.set(text.into());
1674        self.anchor_position_byte_offset.set(anchor as i32);
1675
1676        self.add_undo_item(UndoItem {
1677            pos: anchor,
1678            text: removed_text,
1679            cursor: real_cursor,
1680            anchor: real_anchor,
1681            kind: UndoItemKind::TextRemove,
1682        });
1683
1684        if trigger_callbacks == TextChangeNotify::TriggerCallbacks {
1685            self.set_cursor_position(
1686                anchor as i32,
1687                true,
1688                trigger_callbacks,
1689                window_adapter,
1690                self_rc,
1691            );
1692            Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
1693        } else {
1694            self.cursor_position_byte_offset.set(anchor as i32);
1695        }
1696    }
1697
1698    pub fn anchor_position(self: Pin<&Self>, text: &str) -> usize {
1699        safe_byte_offset(self.anchor_position_byte_offset(), text)
1700    }
1701
1702    pub fn cursor_position(self: Pin<&Self>, text: &str) -> usize {
1703        safe_byte_offset(self.cursor_position_byte_offset(), text)
1704    }
1705
1706    fn ime_properties(
1707        self: Pin<&Self>,
1708        window_adapter: &Rc<dyn WindowAdapter>,
1709        self_rc: &ItemRc,
1710    ) -> InputMethodProperties {
1711        let text = self.text();
1712        WindowInner::from_pub(window_adapter.window()).last_ime_text.replace(text.clone());
1713        let cursor_position = self.cursor_position(&text);
1714        let anchor_position = self.anchor_position(&text);
1715        let cursor_relative =
1716            self.cursor_rect_for_byte_offset(cursor_position, window_adapter, self_rc);
1717        let geometry = self_rc.geometry();
1718        let origin = self_rc.map_to_window(geometry.origin);
1719        let origin_vector = origin.to_vector();
1720        let cursor_rect_origin =
1721            crate::api::LogicalPosition::from_euclid(cursor_relative.origin + origin_vector);
1722        let cursor_rect_size = crate::api::LogicalSize::from_euclid(cursor_relative.size);
1723        let anchor_point = crate::api::LogicalPosition::from_euclid(
1724            self.cursor_rect_for_byte_offset(anchor_position, window_adapter, self_rc).origin
1725                + origin_vector
1726                + cursor_relative.size,
1727        );
1728        let maybe_parent =
1729            self_rc.parent_item(crate::item_tree::ParentItemTraversalMode::StopAtPopups);
1730        let clip_rect = maybe_parent.map(|parent| {
1731            let geom = parent.geometry();
1732            LogicalRect::new(parent.map_to_window(geom.origin), geom.size)
1733        });
1734
1735        InputMethodProperties {
1736            text,
1737            cursor_position,
1738            anchor_position: (cursor_position != anchor_position).then_some(anchor_position),
1739            preedit_text: self.preedit_text(),
1740            preedit_offset: cursor_position,
1741            cursor_rect_origin,
1742            cursor_rect_size,
1743            anchor_point,
1744            input_type: self.input_type(),
1745            clip_rect,
1746        }
1747    }
1748
1749    // Avoid accessing self.cursor_position()/self.anchor_position() directly, always
1750    // use this bounds-checking function.
1751    pub fn selection_anchor_and_cursor(self: Pin<&Self>) -> (usize, usize) {
1752        let text = self.text();
1753        let cursor_pos = self.cursor_position(&text);
1754        let anchor_pos = self.anchor_position(&text);
1755
1756        if anchor_pos > cursor_pos {
1757            (cursor_pos as _, anchor_pos as _)
1758        } else {
1759            (anchor_pos as _, cursor_pos as _)
1760        }
1761    }
1762
1763    pub fn has_selection(self: Pin<&Self>) -> bool {
1764        let (anchor_pos, cursor_pos) = self.selection_anchor_and_cursor();
1765        anchor_pos != cursor_pos
1766    }
1767
1768    fn insert(
1769        self: Pin<&Self>,
1770        text_to_insert: &str,
1771        window_adapter: &Rc<dyn WindowAdapter>,
1772        self_rc: &ItemRc,
1773    ) {
1774        if text_to_insert.is_empty() {
1775            return;
1776        }
1777
1778        let (real_cursor, real_anchor) = {
1779            let text = self.text();
1780            (self.cursor_position(&text), self.anchor_position(&text))
1781        };
1782
1783        self.delete_selection(window_adapter, self_rc, TextChangeNotify::SkipCallbacks);
1784        let mut text: String = self.text().into();
1785        let cursor_pos = self.selection_anchor_and_cursor().1;
1786        let mut inserted_text: SharedString = text_to_insert.into();
1787        if text_to_insert.contains('\n') && self.single_line() {
1788            inserted_text = text_to_insert.replace('\n', " ").into();
1789            text.insert_str(cursor_pos, &inserted_text);
1790        } else {
1791            text.insert_str(cursor_pos, text_to_insert);
1792        }
1793
1794        self.add_undo_item(UndoItem {
1795            pos: cursor_pos,
1796            text: inserted_text,
1797            cursor: real_cursor,
1798            anchor: real_anchor,
1799            kind: UndoItemKind::TextInsert,
1800        });
1801
1802        let cursor_pos = cursor_pos + text_to_insert.len();
1803        self.text.set(text.into());
1804        self.anchor_position_byte_offset.set(cursor_pos as i32);
1805        self.set_cursor_position(
1806            cursor_pos as i32,
1807            true,
1808            TextChangeNotify::TriggerCallbacks,
1809            window_adapter,
1810            self_rc,
1811        );
1812        Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
1813    }
1814
1815    pub fn cut(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1816        self.copy(window_adapter, self_rc);
1817        self.delete_selection(window_adapter, self_rc, TextChangeNotify::TriggerCallbacks);
1818    }
1819
1820    pub fn set_selection_offsets(
1821        self: Pin<&Self>,
1822        window_adapter: &Rc<dyn WindowAdapter>,
1823        self_rc: &ItemRc,
1824        start: i32,
1825        end: i32,
1826    ) {
1827        let text = self.text();
1828        let safe_start = safe_byte_offset(start, &text);
1829        let safe_end = safe_byte_offset(end, &text);
1830
1831        self.as_ref().anchor_position_byte_offset.set(safe_start as i32);
1832        self.set_cursor_position(
1833            safe_end as i32,
1834            true,
1835            TextChangeNotify::TriggerCallbacks,
1836            window_adapter,
1837            self_rc,
1838        );
1839    }
1840
1841    pub fn select_all(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1842        self.move_cursor(
1843            TextCursorDirection::StartOfText,
1844            AnchorMode::MoveAnchor,
1845            TextChangeNotify::SkipCallbacks,
1846            window_adapter,
1847            self_rc,
1848        );
1849        self.move_cursor(
1850            TextCursorDirection::EndOfText,
1851            AnchorMode::KeepAnchor,
1852            TextChangeNotify::TriggerCallbacks,
1853            window_adapter,
1854            self_rc,
1855        );
1856    }
1857
1858    pub fn clear_selection(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
1859        self.as_ref().anchor_position_byte_offset.set(self.as_ref().cursor_position_byte_offset());
1860    }
1861
1862    pub fn select_word(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1863        let text = self.text();
1864        let anchor = self.anchor_position(&text);
1865        let cursor = self.cursor_position(&text);
1866        let (new_a, new_c) = if anchor <= cursor {
1867            (prev_word_boundary(&text, anchor), next_word_boundary(&text, cursor))
1868        } else {
1869            (next_word_boundary(&text, anchor), prev_word_boundary(&text, cursor))
1870        };
1871        self.as_ref().anchor_position_byte_offset.set(new_a as i32);
1872        self.set_cursor_position(
1873            new_c as i32,
1874            true,
1875            TextChangeNotify::TriggerCallbacks,
1876            window_adapter,
1877            self_rc,
1878        );
1879    }
1880
1881    fn select_paragraph(
1882        self: Pin<&Self>,
1883        window_adapter: &Rc<dyn WindowAdapter>,
1884        self_rc: &ItemRc,
1885    ) {
1886        let text = self.text();
1887        let anchor = self.anchor_position(&text);
1888        let cursor = self.cursor_position(&text);
1889        let (new_a, new_c) = if anchor <= cursor {
1890            (prev_paragraph_boundary(&text, anchor), next_paragraph_boundary(&text, cursor))
1891        } else {
1892            (next_paragraph_boundary(&text, anchor), prev_paragraph_boundary(&text, cursor))
1893        };
1894        self.as_ref().anchor_position_byte_offset.set(new_a as i32);
1895        self.set_cursor_position(
1896            new_c as i32,
1897            true,
1898            TextChangeNotify::TriggerCallbacks,
1899            window_adapter,
1900            self_rc,
1901        );
1902    }
1903
1904    pub fn copy(self: Pin<&Self>, w: &Rc<dyn WindowAdapter>, _: &ItemRc) {
1905        self.copy_clipboard(w, Clipboard::DefaultClipboard);
1906    }
1907
1908    fn copy_clipboard(
1909        self: Pin<&Self>,
1910        window_adapter: &Rc<dyn WindowAdapter>,
1911        clipboard: Clipboard,
1912    ) {
1913        let (anchor, cursor) = self.selection_anchor_and_cursor();
1914        if anchor == cursor {
1915            return;
1916        }
1917        let text = self.text();
1918
1919        WindowInner::from_pub(window_adapter.window())
1920            .ctx
1921            .platform()
1922            .set_clipboard_text(&text[anchor..cursor], clipboard);
1923    }
1924
1925    pub fn paste(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1926        self.paste_clipboard(window_adapter, self_rc, Clipboard::DefaultClipboard);
1927    }
1928
1929    fn paste_clipboard(
1930        self: Pin<&Self>,
1931        window_adapter: &Rc<dyn WindowAdapter>,
1932        self_rc: &ItemRc,
1933        clipboard: Clipboard,
1934    ) {
1935        if let Some(text) =
1936            WindowInner::from_pub(window_adapter.window()).ctx.platform().clipboard_text(clipboard)
1937        {
1938            self.preedit_text.set(Default::default());
1939            self.insert(&text, window_adapter, self_rc);
1940        }
1941    }
1942
1943    pub fn font_request(self: Pin<&Self>, self_rc: &ItemRc) -> FontRequest {
1944        WindowItem::resolved_font_request(
1945            self_rc,
1946            self.font_family(),
1947            self.font_weight(),
1948            self.font_size(),
1949            self.letter_spacing(),
1950            self.font_italic(),
1951        )
1952    }
1953
1954    /// Returns a [`TextInputVisualRepresentation`] struct that contains all the fields necessary for rendering the text input,
1955    /// after making adjustments such as applying a substitution of characters for password input fields, or making sure
1956    /// that the selection start is always less or equal than the selection end.
1957    pub fn visual_representation(
1958        self: Pin<&Self>,
1959        password_character_fn: Option<fn() -> char>,
1960    ) -> TextInputVisualRepresentation {
1961        let mut text: String = self.text().into();
1962
1963        let preedit_text = self.preedit_text();
1964        let (preedit_range, selection_range, cursor_position) = if !preedit_text.is_empty() {
1965            let cursor_position = self.cursor_position(&text);
1966
1967            text.insert_str(cursor_position, &preedit_text);
1968            let preedit_range = cursor_position..cursor_position + preedit_text.len();
1969
1970            if let Some(preedit_sel) = self.preedit_selection().as_option() {
1971                let preedit_selection = cursor_position + preedit_sel.start as usize
1972                    ..cursor_position + preedit_sel.end as usize;
1973                (preedit_range, preedit_selection, Some(cursor_position + preedit_sel.end as usize))
1974            } else {
1975                let cur = preedit_range.end;
1976                (preedit_range, cur..cur, None)
1977            }
1978        } else {
1979            let preedit_range = Default::default();
1980            let (selection_anchor_pos, selection_cursor_pos) = self.selection_anchor_and_cursor();
1981            let selection_range = selection_anchor_pos..selection_cursor_pos;
1982            let cursor_position = self.cursor_position(&text);
1983            let cursor_visible = self.cursor_visible() && self.enabled() && !self.read_only();
1984            let cursor_position = if cursor_visible && selection_range.is_empty() {
1985                Some(cursor_position)
1986            } else {
1987                None
1988            };
1989            (preedit_range, selection_range, cursor_position)
1990        };
1991
1992        let text_color = self.color();
1993
1994        let cursor_color = if cfg!(any(target_os = "android", target_vendor = "apple")) {
1995            if cursor_position.is_some() {
1996                self.selection_background_color().with_alpha(1.)
1997            } else {
1998                Default::default()
1999            }
2000        } else {
2001            text_color.color()
2002        };
2003
2004        let mut repr = TextInputVisualRepresentation {
2005            text,
2006            preedit_range,
2007            selection_range,
2008            cursor_position,
2009            text_without_password: None,
2010            password_character: Default::default(),
2011            text_color,
2012            cursor_color,
2013        };
2014        repr.apply_password_character_substitution(self, password_character_fn);
2015        repr
2016    }
2017
2018    fn cursor_rect_for_byte_offset(
2019        self: Pin<&Self>,
2020        byte_offset: usize,
2021        window_adapter: &Rc<dyn WindowAdapter>,
2022        self_rc: &ItemRc,
2023    ) -> LogicalRect {
2024        window_adapter.renderer().text_input_cursor_rect_for_byte_offset(self, self_rc, byte_offset)
2025    }
2026
2027    pub fn byte_offset_for_position(
2028        self: Pin<&Self>,
2029        pos: LogicalPoint,
2030        window_adapter: &Rc<dyn WindowAdapter>,
2031        self_rc: &ItemRc,
2032    ) -> usize {
2033        window_adapter.renderer().text_input_byte_offset_for_position(self, self_rc, pos)
2034    }
2035
2036    /// When pressing the mouse (or releasing the finger, on android) we should take the focus if we don't have it already.
2037    /// Setting the focus will show the virtual keyboard, otherwise we should make sure that the keyboard is shown if it was hidden by the user
2038    fn ensure_focus_and_ime(
2039        self: Pin<&Self>,
2040        window_adapter: &Rc<dyn WindowAdapter>,
2041        self_rc: &ItemRc,
2042    ) {
2043        if !self.has_focus() {
2044            WindowInner::from_pub(window_adapter.window()).set_focus_item(
2045                self_rc,
2046                true,
2047                FocusReason::PointerClick,
2048            );
2049        } else if !self.read_only() {
2050            if let Some(w) = window_adapter.internal(crate::InternalToken) {
2051                w.input_method_request(InputMethodRequest::Enable(
2052                    self.ime_properties(window_adapter, self_rc),
2053                ));
2054            }
2055        }
2056    }
2057
2058    fn add_undo_item(self: Pin<&Self>, item: UndoItem) {
2059        let mut items = self.undo_items.take();
2060        // try to merge with the last item
2061        if let Some(last) = items.make_mut_slice().last_mut() {
2062            match (&item.kind, &last.kind) {
2063                (UndoItemKind::TextInsert, UndoItemKind::TextInsert) => {
2064                    let is_new_line = item.text == "\n";
2065                    let last_is_new_line = last.text == "\n";
2066                    // if the last item or current item is a new_line
2067                    // we insert it as a standalone item, no merging
2068                    if item.pos == last.pos + last.text.len() && !is_new_line && !last_is_new_line {
2069                        last.text += &item.text;
2070                    } else {
2071                        items.push(item);
2072                    }
2073                }
2074                (UndoItemKind::TextRemove, UndoItemKind::TextRemove) => {
2075                    if item.pos + item.text.len() == last.pos {
2076                        last.pos = item.pos;
2077                        let old_text = last.text.clone();
2078                        last.text = item.text;
2079                        last.text += &old_text;
2080                        // prepend
2081                    } else {
2082                        items.push(item);
2083                    }
2084                }
2085                _ => {
2086                    items.push(item);
2087                }
2088            }
2089        } else {
2090            items.push(item);
2091        }
2092
2093        self.undo_items.set(items);
2094    }
2095
2096    fn undo(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
2097        let mut items = self.undo_items.take();
2098        let Some(last) = items.pop() else {
2099            return;
2100        };
2101
2102        match last.kind {
2103            UndoItemKind::TextInsert => {
2104                let text: String = self.text().into();
2105                let text = [text.split_at(last.pos).0, text.split_at(last.pos + last.text.len()).1]
2106                    .concat();
2107                self.text.set(text.into());
2108
2109                self.anchor_position_byte_offset.set(last.anchor as i32);
2110                self.set_cursor_position(
2111                    last.cursor as i32,
2112                    true,
2113                    TextChangeNotify::TriggerCallbacks,
2114                    window_adapter,
2115                    self_rc,
2116                );
2117            }
2118            UndoItemKind::TextRemove => {
2119                let mut text: String = self.text().into();
2120                text.insert_str(last.pos, &last.text);
2121                self.text.set(text.into());
2122
2123                self.anchor_position_byte_offset.set(last.anchor as i32);
2124                self.set_cursor_position(
2125                    last.cursor as i32,
2126                    true,
2127                    TextChangeNotify::TriggerCallbacks,
2128                    window_adapter,
2129                    self_rc,
2130                );
2131            }
2132        }
2133        self.undo_items.set(items);
2134
2135        let mut redo = self.redo_items.take();
2136        redo.push(last);
2137        self.redo_items.set(redo);
2138        Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
2139    }
2140
2141    fn redo(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
2142        let mut items = self.redo_items.take();
2143        let Some(last) = items.pop() else {
2144            return;
2145        };
2146
2147        match last.kind {
2148            UndoItemKind::TextInsert => {
2149                let mut text: String = self.text().into();
2150                text.insert_str(last.pos, &last.text);
2151                self.text.set(text.into());
2152
2153                self.anchor_position_byte_offset.set(last.anchor as i32);
2154                self.set_cursor_position(
2155                    last.cursor as i32,
2156                    true,
2157                    TextChangeNotify::TriggerCallbacks,
2158                    window_adapter,
2159                    self_rc,
2160                );
2161            }
2162            UndoItemKind::TextRemove => {
2163                let text: String = self.text().into();
2164                let text = [text.split_at(last.pos).0, text.split_at(last.pos + last.text.len()).1]
2165                    .concat();
2166                self.text.set(text.into());
2167
2168                self.anchor_position_byte_offset.set(last.anchor as i32);
2169                self.set_cursor_position(
2170                    last.cursor as i32,
2171                    true,
2172                    TextChangeNotify::TriggerCallbacks,
2173                    window_adapter,
2174                    self_rc,
2175                );
2176            }
2177        }
2178
2179        self.redo_items.set(items);
2180
2181        let mut undo_items = self.undo_items.take();
2182        undo_items.push(last);
2183        self.undo_items.set(undo_items);
2184        Self::FIELD_OFFSETS.edited.apply_pin(self).call(&());
2185    }
2186
2187    pub fn font_metrics(
2188        self: Pin<&Self>,
2189        window_adapter: &Rc<dyn WindowAdapter>,
2190        self_rc: &ItemRc,
2191    ) -> FontMetrics {
2192        let font_request = self.font_request(self_rc);
2193        window_adapter.renderer().font_metrics(font_request)
2194    }
2195
2196    fn accept_text_input(self: Pin<&Self>, text_to_insert: &str) -> bool {
2197        let input_type = self.input_type();
2198        if input_type == InputType::Number && !text_to_insert.chars().all(|ch| ch.is_ascii_digit())
2199        {
2200            return false;
2201        } else if input_type == InputType::Decimal {
2202            let (a, c) = self.selection_anchor_and_cursor();
2203            let text = self.text();
2204            let text = [&text[..a], text_to_insert, &text[c..]].concat();
2205            if text.as_str() != "." && text.as_str() != "-" && text.parse::<f64>().is_err() {
2206                return false;
2207            }
2208        }
2209        true
2210    }
2211}
2212
2213fn next_paragraph_boundary(text: &str, last_cursor_pos: usize) -> usize {
2214    text.as_bytes()
2215        .iter()
2216        .enumerate()
2217        .skip(last_cursor_pos)
2218        .find(|(_, c)| **c == b'\n')
2219        .map(|(new_pos, _)| new_pos)
2220        .unwrap_or(text.len())
2221}
2222
2223fn prev_paragraph_boundary(text: &str, last_cursor_pos: usize) -> usize {
2224    text.as_bytes()
2225        .iter()
2226        .enumerate()
2227        .rev()
2228        .skip(text.len() - last_cursor_pos)
2229        .find(|(_, c)| **c == b'\n')
2230        .map(|(new_pos, _)| new_pos + 1)
2231        .unwrap_or(0)
2232}
2233
2234fn prev_word_boundary(text: &str, last_cursor_pos: usize) -> usize {
2235    let mut word_offset = 0;
2236
2237    for (current_word_offset, _) in text.unicode_word_indices() {
2238        if current_word_offset <= last_cursor_pos {
2239            word_offset = current_word_offset;
2240        } else {
2241            break;
2242        }
2243    }
2244
2245    word_offset
2246}
2247
2248fn next_word_boundary(text: &str, last_cursor_pos: usize) -> usize {
2249    text.unicode_word_indices()
2250        .find(|(offset, slice)| *offset + slice.len() >= last_cursor_pos)
2251        .map_or(text.len(), |(offset, slice)| offset + slice.len())
2252}
2253
2254#[cfg(feature = "ffi")]
2255#[unsafe(no_mangle)]
2256pub unsafe extern "C" fn slint_textinput_set_selection_offsets(
2257    text_input: Pin<&TextInput>,
2258    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2259    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2260    self_index: u32,
2261    start: i32,
2262    end: i32,
2263) {
2264    unsafe {
2265        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2266        let self_rc = ItemRc::new(self_component.clone(), self_index);
2267        text_input.set_selection_offsets(window_adapter, &self_rc, start, end);
2268    }
2269}
2270
2271#[cfg(feature = "ffi")]
2272#[unsafe(no_mangle)]
2273pub unsafe extern "C" fn slint_textinput_select_all(
2274    text_input: Pin<&TextInput>,
2275    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2276    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2277    self_index: u32,
2278) {
2279    unsafe {
2280        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2281        let self_rc = ItemRc::new(self_component.clone(), self_index);
2282        text_input.select_all(window_adapter, &self_rc);
2283    }
2284}
2285
2286#[cfg(feature = "ffi")]
2287#[unsafe(no_mangle)]
2288pub unsafe extern "C" fn slint_textinput_clear_selection(
2289    text_input: Pin<&TextInput>,
2290    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2291    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2292    self_index: u32,
2293) {
2294    unsafe {
2295        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2296        let self_rc = ItemRc::new(self_component.clone(), self_index);
2297        text_input.clear_selection(window_adapter, &self_rc);
2298    }
2299}
2300
2301#[cfg(feature = "ffi")]
2302#[unsafe(no_mangle)]
2303pub unsafe extern "C" fn slint_textinput_cut(
2304    text_input: Pin<&TextInput>,
2305    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2306    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2307    self_index: u32,
2308) {
2309    unsafe {
2310        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2311        let self_rc = ItemRc::new(self_component.clone(), self_index);
2312        text_input.cut(window_adapter, &self_rc);
2313    }
2314}
2315
2316#[cfg(feature = "ffi")]
2317#[unsafe(no_mangle)]
2318pub unsafe extern "C" fn slint_textinput_copy(
2319    text_input: Pin<&TextInput>,
2320    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2321    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2322    self_index: u32,
2323) {
2324    unsafe {
2325        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2326        let self_rc = ItemRc::new(self_component.clone(), self_index);
2327        text_input.copy(window_adapter, &self_rc);
2328    }
2329}
2330
2331#[cfg(feature = "ffi")]
2332#[unsafe(no_mangle)]
2333pub unsafe extern "C" fn slint_textinput_paste(
2334    text_input: Pin<&TextInput>,
2335    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2336    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2337    self_index: u32,
2338) {
2339    unsafe {
2340        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2341        let self_rc = ItemRc::new(self_component.clone(), self_index);
2342        text_input.paste(window_adapter, &self_rc);
2343    }
2344}
2345
2346pub fn slint_text_item_fontmetrics(
2347    window_adapter: &Rc<dyn WindowAdapter>,
2348    item_ref: Pin<ItemRef<'_>>,
2349    self_rc: &ItemRc,
2350) -> FontMetrics {
2351    if let Some(simple_text) = ItemRef::downcast_pin::<SimpleText>(item_ref) {
2352        simple_text.font_metrics(window_adapter, self_rc)
2353    } else if let Some(complex_text) = ItemRef::downcast_pin::<ComplexText>(item_ref) {
2354        complex_text.font_metrics(window_adapter, self_rc)
2355    } else if let Some(text_input) = ItemRef::downcast_pin::<TextInput>(item_ref) {
2356        text_input.font_metrics(window_adapter, self_rc)
2357    } else {
2358        Default::default()
2359    }
2360}
2361
2362#[cfg(feature = "ffi")]
2363#[unsafe(no_mangle)]
2364pub unsafe extern "C" fn slint_cpp_text_item_fontmetrics(
2365    window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2366    self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2367    self_index: u32,
2368) -> FontMetrics {
2369    unsafe {
2370        let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2371        let self_rc = ItemRc::new(self_component.clone(), self_index);
2372        let self_ref = self_rc.borrow();
2373        slint_text_item_fontmetrics(window_adapter, self_ref, &self_rc)
2374    }
2375}