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