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