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