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