Skip to main content

i_slint_core/items/
text.rs

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