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