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