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(any(target_os = "android", target_os = "ios")))]
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(any(target_os = "android", target_os = "ios")))]
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(any(target_os = "android", target_os = "ios"))]
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_vendor = "apple"))]
1317 key_codes::Home => Self::StartOfLine,
1318 #[cfg(not(target_vendor = "apple"))]
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 cursor_uses_selection_color() -> bool {
1439 matches!(
1440 crate::detect_operating_system(),
1441 crate::items::OperatingSystemType::Android
1442 | crate::items::OperatingSystemType::Ios
1443 | crate::items::OperatingSystemType::Macos
1444 )
1445}
1446
1447impl TextInput {
1448 fn show_cursor(&self, window_adapter: &Rc<dyn WindowAdapter>) {
1449 WindowInner::from_pub(window_adapter.window())
1450 .set_cursor_blink_binding(&self.cursor_visible);
1451 }
1452
1453 fn hide_cursor(&self) {
1454 self.cursor_visible.set(false);
1455 }
1456
1457 fn move_cursor(
1459 self: Pin<&Self>,
1460 direction: TextCursorDirection,
1461 anchor_mode: AnchorMode,
1462 trigger_callbacks: TextChangeNotify,
1463 window_adapter: &Rc<dyn WindowAdapter>,
1464 self_rc: &ItemRc,
1465 ) -> bool {
1466 let text = self.text();
1467 if text.is_empty() {
1468 return false;
1469 }
1470
1471 let (anchor, cursor) = self.selection_anchor_and_cursor();
1472 let last_cursor_pos = self.cursor_position(&text);
1473
1474 let mut grapheme_cursor =
1475 unicode_segmentation::GraphemeCursor::new(last_cursor_pos, text.len(), true);
1476
1477 let font_height = window_adapter.renderer().char_size(self, self_rc, ' ').height;
1478
1479 let mut reset_preferred_x_pos = true;
1480
1481 let new_cursor_pos = match direction {
1482 TextCursorDirection::Forward => {
1483 if anchor == cursor || anchor_mode == AnchorMode::KeepAnchor {
1484 grapheme_cursor
1485 .next_boundary(&text, 0)
1486 .ok()
1487 .flatten()
1488 .unwrap_or_else(|| text.len())
1489 } else {
1490 cursor
1491 }
1492 }
1493 TextCursorDirection::Backward => {
1494 if anchor == cursor || anchor_mode == AnchorMode::KeepAnchor {
1495 grapheme_cursor.prev_boundary(&text, 0).ok().flatten().unwrap_or(0)
1496 } else {
1497 anchor
1498 }
1499 }
1500 TextCursorDirection::NextLine => {
1501 reset_preferred_x_pos = false;
1502
1503 let cursor_rect =
1504 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1505 let mut cursor_xy_pos = cursor_rect.center();
1506
1507 cursor_xy_pos.y += font_height;
1508 cursor_xy_pos.x = self.preferred_x_pos.get();
1509 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1510 }
1511 TextCursorDirection::PreviousLine => {
1512 reset_preferred_x_pos = false;
1513
1514 let cursor_rect =
1515 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1516 let mut cursor_xy_pos = cursor_rect.center();
1517
1518 cursor_xy_pos.y -= font_height;
1519 cursor_xy_pos.x = self.preferred_x_pos.get();
1520 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1521 }
1522 TextCursorDirection::PreviousCharacter => {
1523 let mut i = last_cursor_pos;
1524 loop {
1525 i = i.saturating_sub(1);
1526 if text.is_char_boundary(i) {
1527 break i;
1528 }
1529 }
1530 }
1531 TextCursorDirection::ForwardByWord => next_word_boundary(&text, last_cursor_pos + 1),
1533 TextCursorDirection::BackwardByWord => {
1534 prev_word_boundary(&text, last_cursor_pos.saturating_sub(1))
1535 }
1536 TextCursorDirection::StartOfLine => {
1537 let cursor_rect =
1538 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1539 let mut cursor_xy_pos = cursor_rect.center();
1540
1541 cursor_xy_pos.x = 0 as Coord;
1542 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1543 }
1544 TextCursorDirection::EndOfLine => {
1545 let cursor_rect =
1546 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1547 let mut cursor_xy_pos = cursor_rect.center();
1548
1549 cursor_xy_pos.x = Coord::MAX;
1550 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1551 }
1552 TextCursorDirection::StartOfParagraph => {
1553 prev_paragraph_boundary(&text, last_cursor_pos.saturating_sub(1))
1554 }
1555 TextCursorDirection::EndOfParagraph => {
1556 next_paragraph_boundary(&text, last_cursor_pos + 1)
1557 }
1558 TextCursorDirection::StartOfText => 0,
1559 TextCursorDirection::EndOfText => text.len(),
1560 TextCursorDirection::PageUp => {
1561 let offset = self.page_height().get() - font_height;
1562 if offset <= 0 as Coord {
1563 return false;
1564 }
1565 reset_preferred_x_pos = false;
1566 let cursor_rect =
1567 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1568 let mut cursor_xy_pos = cursor_rect.center();
1569 cursor_xy_pos.y -= offset;
1570 cursor_xy_pos.x = self.preferred_x_pos.get();
1571 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1572 }
1573 TextCursorDirection::PageDown => {
1574 let offset = self.page_height().get() - font_height;
1575 if offset <= 0 as Coord {
1576 return false;
1577 }
1578 reset_preferred_x_pos = false;
1579 let cursor_rect =
1580 self.cursor_rect_for_byte_offset(last_cursor_pos, window_adapter, self_rc);
1581 let mut cursor_xy_pos = cursor_rect.center();
1582 cursor_xy_pos.y += offset;
1583 cursor_xy_pos.x = self.preferred_x_pos.get();
1584 self.byte_offset_for_position(cursor_xy_pos, window_adapter, self_rc)
1585 }
1586 };
1587
1588 match anchor_mode {
1589 AnchorMode::KeepAnchor => {}
1590 AnchorMode::MoveAnchor => {
1591 self.as_ref().anchor_position_byte_offset.set(new_cursor_pos as i32);
1592 }
1593 }
1594 self.set_cursor_position(
1595 new_cursor_pos as i32,
1596 reset_preferred_x_pos,
1597 trigger_callbacks,
1598 window_adapter,
1599 self_rc,
1600 );
1601
1602 self.as_ref().show_cursor(window_adapter);
1605
1606 new_cursor_pos != last_cursor_pos
1607 }
1608
1609 pub fn set_cursor_position(
1610 self: Pin<&Self>,
1611 new_position: i32,
1612 reset_preferred_x_pos: bool,
1613 trigger_callbacks: TextChangeNotify,
1614 window_adapter: &Rc<dyn WindowAdapter>,
1615 self_rc: &ItemRc,
1616 ) {
1617 self.cursor_position_byte_offset.set(new_position);
1618 if new_position >= 0 {
1619 let pos = self
1620 .cursor_rect_for_byte_offset(new_position as usize, window_adapter, self_rc)
1621 .origin;
1622 if reset_preferred_x_pos {
1623 self.preferred_x_pos.set(pos.x);
1624 }
1625 if trigger_callbacks == TextChangeNotify::TriggerCallbacks {
1626 Self::FIELD_OFFSETS
1627 .cursor_position_changed()
1628 .apply_pin(self)
1629 .call(&(crate::api::LogicalPosition::from_euclid(pos),));
1630 self.update_ime(window_adapter, self_rc);
1631 }
1632 }
1633 }
1634
1635 fn update_ime(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1636 if self.read_only() || !self.has_focus() {
1637 return;
1638 }
1639 if let Some(w) = window_adapter.internal(crate::InternalToken) {
1640 w.input_method_request(InputMethodRequest::Update(
1641 self.ime_properties(window_adapter, self_rc),
1642 ));
1643 }
1644 }
1645
1646 fn select_and_delete(
1647 self: Pin<&Self>,
1648 step: TextCursorDirection,
1649 window_adapter: &Rc<dyn WindowAdapter>,
1650 self_rc: &ItemRc,
1651 ) {
1652 if !self.has_selection() {
1653 self.move_cursor(
1654 step,
1655 AnchorMode::KeepAnchor,
1656 TextChangeNotify::SkipCallbacks,
1657 window_adapter,
1658 self_rc,
1659 );
1660 }
1661 self.delete_selection(window_adapter, self_rc, TextChangeNotify::TriggerCallbacks);
1662 }
1663
1664 pub fn delete_selection(
1665 self: Pin<&Self>,
1666 window_adapter: &Rc<dyn WindowAdapter>,
1667 self_rc: &ItemRc,
1668 trigger_callbacks: TextChangeNotify,
1669 ) {
1670 let text: String = self.text().into();
1671 if text.is_empty() {
1672 return;
1673 }
1674
1675 let (anchor, cursor) = self.selection_anchor_and_cursor();
1676 if anchor == cursor {
1677 return;
1678 }
1679
1680 let removed_text: SharedString = text[anchor..cursor].into();
1681 let (real_cursor, real_anchor) = {
1683 let text = self.text();
1684 (self.cursor_position(&text), self.anchor_position(&text))
1685 };
1686
1687 let text = [text.split_at(anchor).0, text.split_at(cursor).1].concat();
1688 self.text.set(text.into());
1689 self.anchor_position_byte_offset.set(anchor as i32);
1690
1691 self.add_undo_item(UndoItem {
1692 pos: anchor,
1693 text: removed_text,
1694 cursor: real_cursor,
1695 anchor: real_anchor,
1696 kind: UndoItemKind::TextRemove,
1697 });
1698
1699 if trigger_callbacks == TextChangeNotify::TriggerCallbacks {
1700 self.set_cursor_position(
1701 anchor as i32,
1702 true,
1703 trigger_callbacks,
1704 window_adapter,
1705 self_rc,
1706 );
1707 Self::FIELD_OFFSETS.edited().apply_pin(self).call(&());
1708 } else {
1709 self.cursor_position_byte_offset.set(anchor as i32);
1710 }
1711 }
1712
1713 pub fn anchor_position(self: Pin<&Self>, text: &str) -> usize {
1714 safe_byte_offset(self.anchor_position_byte_offset(), text)
1715 }
1716
1717 pub fn cursor_position(self: Pin<&Self>, text: &str) -> usize {
1718 safe_byte_offset(self.cursor_position_byte_offset(), text)
1719 }
1720
1721 fn ime_properties(
1722 self: Pin<&Self>,
1723 window_adapter: &Rc<dyn WindowAdapter>,
1724 self_rc: &ItemRc,
1725 ) -> InputMethodProperties {
1726 let text = self.text();
1727 WindowInner::from_pub(window_adapter.window()).last_ime_text.replace(text.clone());
1728 let cursor_position = self.cursor_position(&text);
1729 let anchor_position = self.anchor_position(&text);
1730 let cursor_relative =
1731 self.cursor_rect_for_byte_offset(cursor_position, window_adapter, self_rc);
1732 let geometry = self_rc.geometry();
1733 let origin = self_rc.map_to_native_window(geometry.origin);
1734 let origin_vector = origin.to_vector();
1735 let cursor_rect_origin =
1736 crate::api::LogicalPosition::from_euclid(cursor_relative.origin + origin_vector);
1737 let cursor_rect_size = crate::api::LogicalSize::from_euclid(cursor_relative.size);
1738 let anchor_point = crate::api::LogicalPosition::from_euclid(
1739 self.cursor_rect_for_byte_offset(anchor_position, window_adapter, self_rc).origin
1740 + origin_vector
1741 + cursor_relative.size,
1742 );
1743 let maybe_parent =
1744 self_rc.parent_item(crate::item_tree::ParentItemTraversalMode::StopAtPopups);
1745 let clip_rect = maybe_parent.map(|parent| {
1746 let geom = parent.geometry();
1747 LogicalRect::new(parent.map_to_native_window(geom.origin), geom.size)
1748 });
1749
1750 InputMethodProperties {
1751 text,
1752 cursor_position,
1753 anchor_position: (cursor_position != anchor_position).then_some(anchor_position),
1754 preedit_text: self.preedit_text(),
1755 preedit_offset: cursor_position,
1756 cursor_rect_origin,
1757 cursor_rect_size,
1758 anchor_point,
1759 input_type: self.input_type(),
1760 clip_rect,
1761 }
1762 }
1763
1764 pub fn selection_anchor_and_cursor(self: Pin<&Self>) -> (usize, usize) {
1767 let text = self.text();
1768 let cursor_pos = self.cursor_position(&text);
1769 let anchor_pos = self.anchor_position(&text);
1770
1771 if anchor_pos > cursor_pos {
1772 (cursor_pos as _, anchor_pos as _)
1773 } else {
1774 (anchor_pos as _, cursor_pos as _)
1775 }
1776 }
1777
1778 pub fn has_selection(self: Pin<&Self>) -> bool {
1779 let (anchor_pos, cursor_pos) = self.selection_anchor_and_cursor();
1780 anchor_pos != cursor_pos
1781 }
1782
1783 fn insert(
1784 self: Pin<&Self>,
1785 text_to_insert: &str,
1786 window_adapter: &Rc<dyn WindowAdapter>,
1787 self_rc: &ItemRc,
1788 ) {
1789 if text_to_insert.is_empty() {
1790 return;
1791 }
1792
1793 let (real_cursor, real_anchor) = {
1794 let text = self.text();
1795 (self.cursor_position(&text), self.anchor_position(&text))
1796 };
1797
1798 self.delete_selection(window_adapter, self_rc, TextChangeNotify::SkipCallbacks);
1799 let mut text: String = self.text().into();
1800 let cursor_pos = self.selection_anchor_and_cursor().1;
1801 let mut inserted_text: SharedString = text_to_insert.into();
1802 if text_to_insert.contains('\n') && self.single_line() {
1803 inserted_text = text_to_insert.replace('\n', " ").into();
1804 text.insert_str(cursor_pos, &inserted_text);
1805 } else {
1806 text.insert_str(cursor_pos, text_to_insert);
1807 }
1808
1809 self.add_undo_item(UndoItem {
1810 pos: cursor_pos,
1811 text: inserted_text,
1812 cursor: real_cursor,
1813 anchor: real_anchor,
1814 kind: UndoItemKind::TextInsert,
1815 });
1816
1817 let cursor_pos = cursor_pos + text_to_insert.len();
1818 self.text.set(text.into());
1819 self.anchor_position_byte_offset.set(cursor_pos as i32);
1820 self.set_cursor_position(
1821 cursor_pos as i32,
1822 true,
1823 TextChangeNotify::TriggerCallbacks,
1824 window_adapter,
1825 self_rc,
1826 );
1827 Self::FIELD_OFFSETS.edited().apply_pin(self).call(&());
1828 }
1829
1830 pub fn cut(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1831 self.copy(window_adapter, self_rc);
1832 self.delete_selection(window_adapter, self_rc, TextChangeNotify::TriggerCallbacks);
1833 }
1834
1835 pub fn set_selection_offsets(
1836 self: Pin<&Self>,
1837 window_adapter: &Rc<dyn WindowAdapter>,
1838 self_rc: &ItemRc,
1839 start: i32,
1840 end: i32,
1841 ) {
1842 let text = self.text();
1843 let safe_start = safe_byte_offset(start, &text);
1844 let safe_end = safe_byte_offset(end, &text);
1845
1846 self.as_ref().anchor_position_byte_offset.set(safe_start as i32);
1847 self.set_cursor_position(
1848 safe_end as i32,
1849 true,
1850 TextChangeNotify::TriggerCallbacks,
1851 window_adapter,
1852 self_rc,
1853 );
1854 }
1855
1856 pub fn select_all(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1857 self.move_cursor(
1858 TextCursorDirection::StartOfText,
1859 AnchorMode::MoveAnchor,
1860 TextChangeNotify::SkipCallbacks,
1861 window_adapter,
1862 self_rc,
1863 );
1864 self.move_cursor(
1865 TextCursorDirection::EndOfText,
1866 AnchorMode::KeepAnchor,
1867 TextChangeNotify::TriggerCallbacks,
1868 window_adapter,
1869 self_rc,
1870 );
1871 }
1872
1873 pub fn clear_selection(self: Pin<&Self>, _: &Rc<dyn WindowAdapter>, _: &ItemRc) {
1874 self.as_ref().anchor_position_byte_offset.set(self.as_ref().cursor_position_byte_offset());
1875 }
1876
1877 pub fn select_word(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1878 let text = self.text();
1879 let anchor = self.anchor_position(&text);
1880 let cursor = self.cursor_position(&text);
1881 let (new_a, new_c) = if anchor <= cursor {
1882 (prev_word_boundary(&text, anchor), next_word_boundary(&text, cursor))
1883 } else {
1884 (next_word_boundary(&text, anchor), prev_word_boundary(&text, cursor))
1885 };
1886 self.as_ref().anchor_position_byte_offset.set(new_a as i32);
1887 self.set_cursor_position(
1888 new_c as i32,
1889 true,
1890 TextChangeNotify::TriggerCallbacks,
1891 window_adapter,
1892 self_rc,
1893 );
1894 }
1895
1896 fn select_paragraph(
1897 self: Pin<&Self>,
1898 window_adapter: &Rc<dyn WindowAdapter>,
1899 self_rc: &ItemRc,
1900 ) {
1901 let text = self.text();
1902 let anchor = self.anchor_position(&text);
1903 let cursor = self.cursor_position(&text);
1904 let (new_a, new_c) = if anchor <= cursor {
1905 (prev_paragraph_boundary(&text, anchor), next_paragraph_boundary(&text, cursor))
1906 } else {
1907 (next_paragraph_boundary(&text, anchor), prev_paragraph_boundary(&text, cursor))
1908 };
1909 self.as_ref().anchor_position_byte_offset.set(new_a as i32);
1910 self.set_cursor_position(
1911 new_c as i32,
1912 true,
1913 TextChangeNotify::TriggerCallbacks,
1914 window_adapter,
1915 self_rc,
1916 );
1917 }
1918
1919 pub fn copy(self: Pin<&Self>, w: &Rc<dyn WindowAdapter>, _: &ItemRc) {
1920 self.copy_clipboard(w, Clipboard::DefaultClipboard);
1921 }
1922
1923 fn copy_clipboard(
1924 self: Pin<&Self>,
1925 window_adapter: &Rc<dyn WindowAdapter>,
1926 clipboard: Clipboard,
1927 ) {
1928 let (anchor, cursor) = self.selection_anchor_and_cursor();
1929 if anchor == cursor {
1930 return;
1931 }
1932 let text = self.text();
1933
1934 WindowInner::from_pub(window_adapter.window())
1935 .context()
1936 .platform()
1937 .set_clipboard_text(&text[anchor..cursor], clipboard);
1938 }
1939
1940 pub fn paste(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
1941 self.paste_clipboard(window_adapter, self_rc, Clipboard::DefaultClipboard);
1942 }
1943
1944 fn paste_clipboard(
1945 self: Pin<&Self>,
1946 window_adapter: &Rc<dyn WindowAdapter>,
1947 self_rc: &ItemRc,
1948 clipboard: Clipboard,
1949 ) {
1950 if let Some(text) = WindowInner::from_pub(window_adapter.window())
1951 .context()
1952 .platform()
1953 .clipboard_text(clipboard)
1954 {
1955 self.preedit_text.set(Default::default());
1956 self.insert(&text, window_adapter, self_rc);
1957 }
1958 }
1959
1960 pub fn visual_representation(
1964 self: Pin<&Self>,
1965 password_character_fn: Option<fn() -> char>,
1966 ) -> TextInputVisualRepresentation {
1967 let mut text = self.text();
1968
1969 let preedit_text = self.preedit_text();
1970 let (preedit_range, selection_range, cursor_position) = if !preedit_text.is_empty() {
1971 let cursor_position = self.cursor_position(&text);
1972
1973 text =
1974 [&text[..cursor_position], &preedit_text, &text[cursor_position..]].concat().into();
1975 let preedit_range = cursor_position..cursor_position + preedit_text.len();
1976
1977 if let Some(preedit_sel) = self.preedit_selection().as_option() {
1978 let preedit_selection = cursor_position + preedit_sel.start as usize
1979 ..cursor_position + preedit_sel.end as usize;
1980 (preedit_range, preedit_selection, Some(cursor_position + preedit_sel.end as usize))
1981 } else {
1982 let cur = preedit_range.end;
1983 (preedit_range, cur..cur, None)
1984 }
1985 } else {
1986 let preedit_range = Default::default();
1987 let (selection_anchor_pos, selection_cursor_pos) = self.selection_anchor_and_cursor();
1988 let selection_range = selection_anchor_pos..selection_cursor_pos;
1989 let cursor_position = self.cursor_position(&text);
1990 let cursor_visible = self.cursor_visible() && self.enabled();
1991 let cursor_position = if cursor_visible && selection_range.is_empty() {
1992 Some(cursor_position)
1993 } else {
1994 None
1995 };
1996 (preedit_range, selection_range, cursor_position)
1997 };
1998
1999 let text_color = self.color();
2000
2001 let cursor_color = if cursor_uses_selection_color() {
2002 if cursor_position.is_some() {
2003 self.selection_background_color().with_alpha(1.)
2004 } else {
2005 Default::default()
2006 }
2007 } else {
2008 text_color.color()
2010 };
2011
2012 let mut repr = TextInputVisualRepresentation {
2013 text,
2014 preedit_range,
2015 selection_range,
2016 cursor_position,
2017 text_without_password: None,
2018 password_character: Default::default(),
2019 text_color,
2020 cursor_color,
2021 };
2022 repr.apply_password_character_substitution(self, password_character_fn);
2023 repr
2024 }
2025
2026 fn cursor_rect_for_byte_offset(
2027 self: Pin<&Self>,
2028 byte_offset: usize,
2029 window_adapter: &Rc<dyn WindowAdapter>,
2030 self_rc: &ItemRc,
2031 ) -> LogicalRect {
2032 window_adapter.renderer().text_input_cursor_rect_for_byte_offset(self, self_rc, byte_offset)
2033 }
2034
2035 pub fn byte_offset_for_position(
2036 self: Pin<&Self>,
2037 pos: LogicalPoint,
2038 window_adapter: &Rc<dyn WindowAdapter>,
2039 self_rc: &ItemRc,
2040 ) -> usize {
2041 window_adapter.renderer().text_input_byte_offset_for_position(self, self_rc, pos)
2042 }
2043
2044 fn ensure_focus_and_ime(
2047 self: Pin<&Self>,
2048 window_adapter: &Rc<dyn WindowAdapter>,
2049 self_rc: &ItemRc,
2050 ) {
2051 if !self.has_focus() {
2052 WindowInner::from_pub(window_adapter.window()).set_focus_item(
2053 self_rc,
2054 true,
2055 FocusReason::PointerClick,
2056 );
2057 } else if !self.read_only()
2058 && let Some(w) = window_adapter.internal(crate::InternalToken)
2059 {
2060 w.input_method_request(InputMethodRequest::Enable(
2061 self.ime_properties(window_adapter, self_rc),
2062 ));
2063 }
2064 }
2065
2066 fn add_undo_item(self: Pin<&Self>, item: UndoItem) {
2067 let mut items = self.undo_items.take();
2068 if let Some(last) = items.make_mut_slice().last_mut() {
2070 match (&item.kind, &last.kind) {
2071 (UndoItemKind::TextInsert, UndoItemKind::TextInsert) => {
2072 let is_new_line = item.text == "\n";
2073 let last_is_new_line = last.text == "\n";
2074 if item.pos == last.pos + last.text.len() && !is_new_line && !last_is_new_line {
2077 last.text += &item.text;
2078 } else {
2079 items.push(item);
2080 }
2081 }
2082 (UndoItemKind::TextRemove, UndoItemKind::TextRemove)
2083 if item.pos + item.text.len() == last.pos =>
2084 {
2085 last.pos = item.pos;
2086 let old_text = last.text.clone();
2087 last.text = item.text;
2088 last.text += &old_text;
2089 }
2091 _ => {
2092 items.push(item);
2093 }
2094 }
2095 } else {
2096 items.push(item);
2097 }
2098
2099 self.undo_items.set(items);
2100 }
2101
2102 pub fn undo(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
2103 let mut items = self.undo_items.take();
2104 let Some(last) = items.pop() else {
2105 return;
2106 };
2107
2108 match last.kind {
2109 UndoItemKind::TextInsert => {
2110 let text: String = self.text().into();
2111 let text = [text.split_at(last.pos).0, text.split_at(last.pos + last.text.len()).1]
2112 .concat();
2113 self.text.set(text.into());
2114
2115 self.anchor_position_byte_offset.set(last.anchor as i32);
2116 self.set_cursor_position(
2117 last.cursor as i32,
2118 true,
2119 TextChangeNotify::TriggerCallbacks,
2120 window_adapter,
2121 self_rc,
2122 );
2123 }
2124 UndoItemKind::TextRemove => {
2125 let mut text: String = self.text().into();
2126 text.insert_str(last.pos, &last.text);
2127 self.text.set(text.into());
2128
2129 self.anchor_position_byte_offset.set(last.anchor as i32);
2130 self.set_cursor_position(
2131 last.cursor as i32,
2132 true,
2133 TextChangeNotify::TriggerCallbacks,
2134 window_adapter,
2135 self_rc,
2136 );
2137 }
2138 }
2139 self.undo_items.set(items);
2140
2141 let mut redo = self.redo_items.take();
2142 redo.push(last);
2143 self.redo_items.set(redo);
2144 Self::FIELD_OFFSETS.edited().apply_pin(self).call(&());
2145 }
2146
2147 pub fn redo(self: Pin<&Self>, window_adapter: &Rc<dyn WindowAdapter>, self_rc: &ItemRc) {
2148 let mut items = self.redo_items.take();
2149 let Some(last) = items.pop() else {
2150 return;
2151 };
2152
2153 match last.kind {
2154 UndoItemKind::TextInsert => {
2155 let mut text: String = self.text().into();
2156 text.insert_str(last.pos, &last.text);
2157 self.text.set(text.into());
2158
2159 self.anchor_position_byte_offset.set(last.anchor as i32);
2160 self.set_cursor_position(
2161 last.cursor as i32,
2162 true,
2163 TextChangeNotify::TriggerCallbacks,
2164 window_adapter,
2165 self_rc,
2166 );
2167 }
2168 UndoItemKind::TextRemove => {
2169 let text: String = self.text().into();
2170 let text = [text.split_at(last.pos).0, text.split_at(last.pos + last.text.len()).1]
2171 .concat();
2172 self.text.set(text.into());
2173
2174 self.anchor_position_byte_offset.set(last.anchor as i32);
2175 self.set_cursor_position(
2176 last.cursor as i32,
2177 true,
2178 TextChangeNotify::TriggerCallbacks,
2179 window_adapter,
2180 self_rc,
2181 );
2182 }
2183 }
2184
2185 self.redo_items.set(items);
2186
2187 let mut undo_items = self.undo_items.take();
2188 undo_items.push(last);
2189 self.undo_items.set(undo_items);
2190 Self::FIELD_OFFSETS.edited().apply_pin(self).call(&());
2191 }
2192
2193 pub fn font_metrics(
2194 self: Pin<&Self>,
2195 window_adapter: &Rc<dyn WindowAdapter>,
2196 self_rc: &ItemRc,
2197 ) -> FontMetrics {
2198 let font_request = self.font_request(self_rc);
2199 window_adapter.renderer().font_metrics(font_request)
2200 }
2201
2202 fn accept_text_input(self: Pin<&Self>, text_to_insert: &str) -> bool {
2203 let input_type = self.input_type();
2204
2205 match input_type {
2206 InputType::Number => return text_to_insert.chars().all(|ch| ch.is_ascii_digit()),
2207 InputType::Decimal => {
2208 let (a, c) = self.selection_anchor_and_cursor();
2209 let current = self.text();
2210 let candidate = [¤t[..a], text_to_insert, ¤t[c..]].concat();
2211
2212 if candidate.len() <= 2
2214 && crate::context::GLOBAL_CONTEXT.with(|ctx| {
2215 let sep =
2216 ctx.get().map(|ctx| ctx.locale_decimal_separator()).unwrap_or('.');
2217 let mut it = candidate.chars();
2218 match (it.next(), it.next()) {
2219 (Some('-'), None) => true,
2220 (Some('-'), Some(c2)) => c2 == sep,
2221 (Some(c1), None) => c1 == sep,
2222 _ => false,
2223 }
2224 })
2225 {
2226 return true;
2227 }
2228 return string_to_float(&candidate).is_some();
2229 }
2230 InputType::Password | InputType::Text | InputType::Search => (),
2231 }
2232
2233 true
2234 }
2235}
2236
2237fn next_paragraph_boundary(text: &str, last_cursor_pos: usize) -> usize {
2238 text.as_bytes()
2239 .iter()
2240 .enumerate()
2241 .skip(last_cursor_pos)
2242 .find(|(_, c)| **c == b'\n')
2243 .map(|(new_pos, _)| new_pos)
2244 .unwrap_or(text.len())
2245}
2246
2247fn prev_paragraph_boundary(text: &str, last_cursor_pos: usize) -> usize {
2248 text.as_bytes()
2249 .iter()
2250 .enumerate()
2251 .rev()
2252 .skip(text.len() - last_cursor_pos)
2253 .find(|(_, c)| **c == b'\n')
2254 .map(|(new_pos, _)| new_pos + 1)
2255 .unwrap_or(0)
2256}
2257
2258fn prev_word_boundary(text: &str, last_cursor_pos: usize) -> usize {
2259 let mut word_offset = 0;
2260
2261 for (current_word_offset, _) in text.unicode_word_indices() {
2262 if current_word_offset <= last_cursor_pos {
2263 word_offset = current_word_offset;
2264 } else {
2265 break;
2266 }
2267 }
2268
2269 word_offset
2270}
2271
2272fn next_word_boundary(text: &str, last_cursor_pos: usize) -> usize {
2273 text.unicode_word_indices()
2274 .find(|(offset, slice)| *offset + slice.len() >= last_cursor_pos)
2275 .map_or(text.len(), |(offset, slice)| offset + slice.len())
2276}
2277
2278#[cfg(feature = "ffi")]
2279#[unsafe(no_mangle)]
2280pub unsafe extern "C" fn slint_textinput_set_selection_offsets(
2281 text_input: Pin<&TextInput>,
2282 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2283 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2284 self_index: u32,
2285 start: i32,
2286 end: i32,
2287) {
2288 unsafe {
2289 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2290 let self_rc = ItemRc::new(self_component.clone(), self_index);
2291 text_input.set_selection_offsets(window_adapter, &self_rc, start, end);
2292 }
2293}
2294
2295#[cfg(feature = "ffi")]
2296#[unsafe(no_mangle)]
2297pub unsafe extern "C" fn slint_textinput_select_all(
2298 text_input: Pin<&TextInput>,
2299 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2300 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2301 self_index: u32,
2302) {
2303 unsafe {
2304 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2305 let self_rc = ItemRc::new(self_component.clone(), self_index);
2306 text_input.select_all(window_adapter, &self_rc);
2307 }
2308}
2309
2310#[cfg(feature = "ffi")]
2311#[unsafe(no_mangle)]
2312pub unsafe extern "C" fn slint_textinput_clear_selection(
2313 text_input: Pin<&TextInput>,
2314 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2315 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2316 self_index: u32,
2317) {
2318 unsafe {
2319 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2320 let self_rc = ItemRc::new(self_component.clone(), self_index);
2321 text_input.clear_selection(window_adapter, &self_rc);
2322 }
2323}
2324
2325#[cfg(feature = "ffi")]
2326#[unsafe(no_mangle)]
2327pub unsafe extern "C" fn slint_textinput_cut(
2328 text_input: Pin<&TextInput>,
2329 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2330 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2331 self_index: u32,
2332) {
2333 unsafe {
2334 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2335 let self_rc = ItemRc::new(self_component.clone(), self_index);
2336 text_input.cut(window_adapter, &self_rc);
2337 }
2338}
2339
2340#[cfg(feature = "ffi")]
2341#[unsafe(no_mangle)]
2342pub unsafe extern "C" fn slint_textinput_copy(
2343 text_input: Pin<&TextInput>,
2344 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2345 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2346 self_index: u32,
2347) {
2348 unsafe {
2349 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2350 let self_rc = ItemRc::new(self_component.clone(), self_index);
2351 text_input.copy(window_adapter, &self_rc);
2352 }
2353}
2354
2355#[cfg(feature = "ffi")]
2356#[unsafe(no_mangle)]
2357pub unsafe extern "C" fn slint_textinput_paste(
2358 text_input: Pin<&TextInput>,
2359 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2360 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2361 self_index: u32,
2362) {
2363 unsafe {
2364 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2365 let self_rc = ItemRc::new(self_component.clone(), self_index);
2366 text_input.paste(window_adapter, &self_rc);
2367 }
2368}
2369
2370#[cfg(feature = "ffi")]
2371#[unsafe(no_mangle)]
2372pub unsafe extern "C" fn slint_textinput_undo(
2373 text_input: Pin<&TextInput>,
2374 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2375 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2376 self_index: u32,
2377) {
2378 unsafe {
2379 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2380 let self_rc = ItemRc::new(self_component.clone(), self_index);
2381 text_input.undo(window_adapter, &self_rc);
2382 }
2383}
2384
2385#[cfg(feature = "ffi")]
2386#[unsafe(no_mangle)]
2387pub unsafe extern "C" fn slint_textinput_redo(
2388 text_input: Pin<&TextInput>,
2389 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2390 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2391 self_index: u32,
2392) {
2393 unsafe {
2394 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2395 let self_rc = ItemRc::new(self_component.clone(), self_index);
2396 text_input.redo(window_adapter, &self_rc);
2397 }
2398}
2399
2400pub fn slint_text_item_fontmetrics(
2401 window_adapter: &Rc<dyn WindowAdapter>,
2402 item_ref: Pin<ItemRef<'_>>,
2403 self_rc: &ItemRc,
2404) -> FontMetrics {
2405 if let Some(simple_text) = ItemRef::downcast_pin::<SimpleText>(item_ref) {
2406 simple_text.font_metrics(window_adapter, self_rc)
2407 } else if let Some(complex_text) = ItemRef::downcast_pin::<ComplexText>(item_ref) {
2408 complex_text.font_metrics(window_adapter, self_rc)
2409 } else if let Some(text_input) = ItemRef::downcast_pin::<TextInput>(item_ref) {
2410 text_input.font_metrics(window_adapter, self_rc)
2411 } else {
2412 Default::default()
2413 }
2414}
2415
2416#[cfg(feature = "ffi")]
2417#[unsafe(no_mangle)]
2418pub unsafe extern "C" fn slint_cpp_text_item_fontmetrics(
2419 window_adapter: *const crate::window::ffi::WindowAdapterRcOpaque,
2420 self_component: &vtable::VRc<crate::item_tree::ItemTreeVTable>,
2421 self_index: u32,
2422) -> FontMetrics {
2423 unsafe {
2424 let window_adapter = &*(window_adapter as *const Rc<dyn WindowAdapter>);
2425 let self_rc = ItemRc::new(self_component.clone(), self_index);
2426 let self_ref = self_rc.borrow();
2427 slint_text_item_fontmetrics(window_adapter, self_ref, &self_rc)
2428 }
2429}