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