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