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