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