i_slint_core/items/
text.rs

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