1use std::sync::Arc;
2
3use emath::{Rect, TSTransform};
4use epaint::text::{Galley, LayoutJob, TextWrapMode, cursor::CCursor};
5
6use crate::{
7 Align, Align2, AtomExt as _, AtomKind, AtomLayout, Atoms, Color32, Context, CursorIcon, Event,
8 EventFilter, FontSelection, Frame, Id, ImeEvent, IntoAtoms, IntoSizedResult, Key,
9 KeyboardShortcut, Margin, Modifiers, NumExt as _, Response, Sense, SizedAtomKind, TextBuffer,
10 TextStyle, Ui, Vec2, Widget, WidgetInfo, WidgetWithState, epaint,
11 os::OperatingSystem,
12 output::OutputEvent,
13 response, text_selection,
14 text_selection::{CCursorRange, text_cursor_state::cursor_rect, visuals::paint_text_selection},
15 vec2,
16};
17
18use super::{TextEditOutput, TextEditState};
19
20type LayouterFn<'t> = &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc<Galley>;
21
22#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
66pub struct TextEdit<'t> {
67 text: &'t mut dyn TextBuffer,
68 prefix: Atoms<'static>,
69 suffix: Atoms<'static>,
70 hint_text: Atoms<'static>,
71 id: Option<Id>,
72 id_salt: Option<Id>,
73 font_selection: FontSelection,
74 text_color: Option<Color32>,
75 layouter: Option<LayouterFn<'t>>,
76 password: bool,
77 frame: Option<Frame>,
78 margin: Margin,
79 multiline: bool,
80 interactive: bool,
81 desired_width: Option<f32>,
82 desired_height_rows: usize,
83 event_filter: EventFilter,
84 cursor_at_end: bool,
85 min_size: Vec2,
86 align: Align2,
87 clip_text: bool,
88 char_limit: usize,
89 return_key: Option<KeyboardShortcut>,
90 background_color: Option<Color32>,
91}
92
93impl WidgetWithState for TextEdit<'_> {
94 type State = TextEditState;
95}
96
97impl TextEdit<'_> {
98 pub fn load_state(ctx: &Context, id: Id) -> Option<TextEditState> {
99 TextEditState::load(ctx, id)
100 }
101
102 pub fn store_state(ctx: &Context, id: Id, state: TextEditState) {
103 state.store(ctx, id);
104 }
105}
106
107impl<'t> TextEdit<'t> {
108 pub fn singleline(text: &'t mut dyn TextBuffer) -> Self {
110 Self {
111 desired_height_rows: 1,
112 multiline: false,
113 clip_text: true,
114 ..Self::multiline(text)
115 }
116 }
117
118 pub fn multiline(text: &'t mut dyn TextBuffer) -> Self {
120 Self {
121 text,
122 prefix: Default::default(),
123 suffix: Default::default(),
124 hint_text: Default::default(),
125 id: None,
126 id_salt: None,
127 font_selection: Default::default(),
128 text_color: None,
129 layouter: None,
130 password: false,
131 frame: None,
132 margin: Margin::symmetric(4, 2),
133 multiline: true,
134 interactive: true,
135 desired_width: None,
136 desired_height_rows: 4,
137 event_filter: EventFilter {
138 horizontal_arrows: true,
140 vertical_arrows: true,
141 tab: false, ..Default::default()
143 },
144 cursor_at_end: true,
145 min_size: Vec2::ZERO,
146 align: Align2::LEFT_TOP,
147 clip_text: false,
148 char_limit: usize::MAX,
149 return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)),
150 background_color: None,
151 }
152 }
153
154 pub fn code_editor(self) -> Self {
159 self.font(TextStyle::Monospace).lock_focus(true)
160 }
161
162 #[inline]
164 pub fn id(mut self, id: Id) -> Self {
165 self.id = Some(id);
166 self
167 }
168
169 #[inline]
171 pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
172 self.id_salt(id_salt)
173 }
174
175 #[inline]
177 pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
178 self.id_salt = Some(Id::new(id_salt));
179 self
180 }
181
182 #[inline]
205 pub fn hint_text(mut self, hint_text: impl IntoAtoms<'static>) -> Self {
206 self.hint_text = hint_text.into_atoms();
207 self
208 }
209
210 #[inline]
212 pub fn prefix(mut self, prefix: impl IntoAtoms<'static>) -> Self {
213 self.prefix = prefix.into_atoms();
214 self
215 }
216
217 #[inline]
219 pub fn suffix(mut self, suffix: impl IntoAtoms<'static>) -> Self {
220 self.suffix = suffix.into_atoms();
221 self
222 }
223
224 #[inline]
227 pub fn background_color(mut self, color: Color32) -> Self {
228 self.background_color = Some(color);
229 self
230 }
231
232 #[inline]
234 pub fn password(mut self, password: bool) -> Self {
235 self.password = password;
236 self
237 }
238
239 #[inline]
241 pub fn font(mut self, font_selection: impl Into<FontSelection>) -> Self {
242 self.font_selection = font_selection.into();
243 self
244 }
245
246 #[inline]
247 pub fn text_color(mut self, text_color: Color32) -> Self {
248 self.text_color = Some(text_color);
249 self
250 }
251
252 #[inline]
253 pub fn text_color_opt(mut self, text_color: Option<Color32>) -> Self {
254 self.text_color = text_color;
255 self
256 }
257
258 #[inline]
282 pub fn layouter(
283 mut self,
284 layouter: &'t mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc<Galley>,
285 ) -> Self {
286 self.layouter = Some(layouter);
287
288 self
289 }
290
291 #[inline]
295 pub fn interactive(mut self, interactive: bool) -> Self {
296 self.interactive = interactive;
297 self
298 }
299
300 #[inline]
302 pub fn frame(mut self, frame: Frame) -> Self {
303 self.frame = Some(frame);
304 self
305 }
306
307 #[inline]
309 pub fn margin(mut self, margin: impl Into<Margin>) -> Self {
310 self.margin = margin.into();
311 self
312 }
313
314 #[inline]
317 pub fn desired_width(mut self, desired_width: f32) -> Self {
318 self.desired_width = Some(desired_width);
319 self
320 }
321
322 #[inline]
326 pub fn desired_rows(mut self, desired_height_rows: usize) -> Self {
327 self.desired_height_rows = desired_height_rows;
328 self
329 }
330
331 #[inline]
337 pub fn lock_focus(mut self, tab_will_indent: bool) -> Self {
338 self.event_filter.tab = tab_will_indent;
339 self
340 }
341
342 #[inline]
346 pub fn cursor_at_end(mut self, b: bool) -> Self {
347 self.cursor_at_end = b;
348 self
349 }
350
351 #[inline]
357 pub fn clip_text(mut self, b: bool) -> Self {
358 if !self.multiline {
360 self.clip_text = b;
361 }
362 self
363 }
364
365 #[inline]
369 pub fn char_limit(mut self, limit: usize) -> Self {
370 self.char_limit = limit;
371 self
372 }
373
374 #[inline]
376 pub fn horizontal_align(mut self, align: Align) -> Self {
377 self.align.0[0] = align;
378 self
379 }
380
381 #[inline]
383 pub fn vertical_align(mut self, align: Align) -> Self {
384 self.align.0[1] = align;
385 self
386 }
387
388 #[inline]
390 pub fn min_size(mut self, min_size: Vec2) -> Self {
391 self.min_size = min_size;
392 self
393 }
394
395 #[inline]
402 pub fn return_key(mut self, return_key: impl Into<Option<KeyboardShortcut>>) -> Self {
403 self.return_key = return_key.into();
404 self
405 }
406}
407
408impl Widget for TextEdit<'_> {
411 fn ui(self, ui: &mut Ui) -> Response {
412 self.show(ui).response.response
413 }
414}
415
416impl TextEdit<'_> {
417 pub fn show(self, ui: &mut Ui) -> TextEditOutput {
433 let TextEdit {
434 text,
435 prefix,
436 suffix,
437 mut hint_text,
438 id,
439 id_salt,
440 font_selection,
441 text_color,
442 layouter,
443 password,
444 frame,
445 margin,
446 multiline,
447 interactive,
448 desired_width,
449 desired_height_rows,
450 event_filter,
451 cursor_at_end,
452 min_size,
453 align,
454 clip_text,
455 char_limit,
456 return_key,
457 background_color,
458 } = self;
459
460 let text_color = text_color
461 .or_else(|| ui.visuals().override_text_color)
462 .unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
464
465 let prev_text = text.as_str().to_owned();
466 let hint_text_str = hint_text.text().unwrap_or_default().to_string();
467
468 let font_id = font_selection.resolve(ui.style());
469 let row_height = ui.fonts_mut(|f| f.row_height(&font_id));
470 const MIN_WIDTH: f32 = 24.0; let available_width = ui.available_width().at_least(MIN_WIDTH);
472 let desired_width = desired_width
473 .unwrap_or_else(|| ui.spacing().text_edit_width)
474 .at_least(min_size.x);
475 let allocate_width = desired_width.at_most(available_width);
476
477 let font_id_clone = font_id.clone();
478 let mut default_layouter = move |ui: &Ui, text: &dyn TextBuffer, wrap_width: f32| {
479 let text = mask_if_password(password, text.as_str());
480 let mut layout_job = if multiline {
481 LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width)
482 } else {
483 LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color)
484 };
485 layout_job.halign = align.x();
486 ui.fonts_mut(|f| f.layout_job(layout_job))
487 };
488
489 let layouter = layouter.unwrap_or(&mut default_layouter);
490
491 let min_inner_height = (desired_height_rows.at_least(1) as f32) * row_height;
492
493 let id = id.unwrap_or_else(|| {
494 if let Some(id_salt) = id_salt {
495 ui.make_persistent_id(id_salt)
496 } else {
497 let id = ui.next_auto_id();
499 ui.skip_ahead_auto_ids(1);
500 id
501 }
502 });
503
504 let allow_drag_to_select =
509 ui.input(|i| !i.has_touch_screen()) || ui.memory(|mem| mem.has_focus(id));
510
511 let sense = if interactive {
512 if allow_drag_to_select {
513 Sense::click_and_drag()
514 } else {
515 Sense::click()
516 }
517 } else {
518 Sense::hover()
519 };
520
521 let mut state = TextEditState::load(ui.ctx(), id).unwrap_or_default();
522 let mut cursor_range = None;
523 let mut prev_cursor_range = None;
524
525 let mut text_changed = false;
526 let text_mutable = text.is_mutable();
527
528 let mut handle_events = |ui: &Ui, galley: &mut Arc<Galley>, layouter, wrap_width, text| {
529 if interactive && ui.memory(|mem| mem.has_focus(id)) {
530 ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter));
531
532 let default_cursor_range = if cursor_at_end {
533 CCursorRange::one(galley.end())
534 } else {
535 CCursorRange::default()
536 };
537 prev_cursor_range = state.cursor.range(galley);
538
539 let (changed, new_cursor_range) = events(
540 ui,
541 &mut state,
542 text,
543 galley,
544 layouter,
545 id,
546 wrap_width,
547 multiline,
548 password,
549 default_cursor_range,
550 char_limit,
551 event_filter,
552 return_key,
553 );
554
555 if changed {
556 text_changed = true;
557 }
558 cursor_range = Some(new_cursor_range);
559 }
560 };
561
562 let mut get_galley = None;
566 let inner_rect_id = Id::new("text_edit_rect");
567 let mut response = {
568 let any_shrink = hint_text.any_shrink();
569 let mut atoms: Atoms<'_> = Atoms::new(());
572
573 for atom in prefix {
576 atoms.push_right(atom);
577 }
578
579 if text.as_str().is_empty() && !hint_text.is_empty() {
580 let mut shrunk = any_shrink;
582 let mut first = true;
583
584 hint_text.map_texts(|t| t.color(ui.style().visuals.weak_text_color()));
587
588 for mut atom in hint_text {
589 if !shrunk && matches!(atom.kind, AtomKind::Text(_)) {
590 atom = atom.atom_shrink(true);
592 atom = atom.atom_grow(true);
593 shrunk = true;
594 }
595
596 if first {
597 atom = atom.atom_id(inner_rect_id);
600 first = false;
601 }
602
603 atoms.push_right(atom.atom_align(Align2::LEFT_TOP));
606 }
607
608 let available_width = allocate_width - margin.sum().x;
611 let galley = layouter(ui, text, available_width);
612
613 let mut galley_clone = Arc::clone(&galley);
617 handle_events(ui, &mut galley_clone, layouter, available_width, text);
618
619 get_galley = Some(galley);
620 } else {
621 let should_shrink = clip_text || multiline;
625
626 atoms.push_right(
630 AtomKind::closure(|ui, args| {
631 let mut galley = layouter(ui, text, args.available_size.x);
632
633 handle_events(ui, &mut galley, layouter, args.available_size.x, text);
637
638 let intrinsic_size = galley.intrinsic_size();
639 let mut size = galley.size();
640 size.y = size.y.at_least(min_inner_height);
641 if clip_text {
642 size.x = size.x.at_most(args.available_size.x);
643 }
644
645 get_galley = Some(galley);
647 IntoSizedResult {
648 intrinsic_size,
649 sized: SizedAtomKind::Empty { size: Some(size) },
650 }
651 })
652 .atom_grow(true)
653 .atom_align(self.align)
654 .atom_id(inner_rect_id)
655 .atom_shrink(should_shrink),
656 );
657 }
658
659 for atom in suffix {
662 atoms.push_right(atom);
663 }
664
665 let custom_frame = frame.is_some();
666 let frame = frame.unwrap_or_else(|| Frame::new().inner_margin(margin));
667
668 let min_height = min_inner_height + frame.total_margin().sum().y;
669
670 let wrap_mode = if multiline {
672 TextWrapMode::Wrap
673 } else {
674 TextWrapMode::Truncate
675 };
676
677 let mut allocated = AtomLayout::new(atoms)
678 .id(id)
679 .min_size(Vec2::new(allocate_width, min_height))
680 .max_width(allocate_width)
681 .sense(sense)
682 .frame(frame)
683 .align2(align)
684 .wrap_mode(wrap_mode)
685 .allocate(ui);
686
687 allocated.frame = if !custom_frame {
688 let visuals = ui.style().interact(&allocated.response);
689 let background_color =
690 background_color.unwrap_or_else(|| ui.visuals().text_edit_bg_color());
691
692 let (corner_radius, background_color, stroke) = if text_mutable {
693 if allocated.response.has_focus() {
694 (
695 visuals.corner_radius,
696 background_color,
697 ui.visuals().selection.stroke,
698 )
699 } else {
700 (visuals.corner_radius, background_color, visuals.bg_stroke)
701 }
702 } else {
703 let visuals = &ui.style().visuals.widgets.inactive;
704 (
705 visuals.corner_radius,
706 Color32::TRANSPARENT,
707 visuals.bg_stroke,
708 )
709 };
710 allocated
711 .frame
712 .fill(background_color)
713 .corner_radius(corner_radius)
714 .inner_margin(
715 allocated.frame.inner_margin
716 + Margin::same((visuals.expansion - stroke.width).round() as i8),
717 )
718 .outer_margin(Margin::same(-(visuals.expansion as i8)))
719 .stroke(stroke)
720 } else {
721 allocated.frame
722 };
723
724 allocated.paint(ui)
725 };
726
727 let inner_rect = response.rect(inner_rect_id).unwrap_or(Rect::ZERO);
728
729 let mut galley = get_galley.expect("Galley should be available here");
731
732 response.flags -= response::Flags::FAKE_PRIMARY_CLICKED;
734 let text_clip_rect = inner_rect;
735 let painter = ui.painter_at(text_clip_rect.expand(1.0)); if interactive && let Some(pointer_pos) = response.interact_pointer_pos() {
738 if response.hovered() && text.is_mutable() {
739 ui.output_mut(|o| o.mutable_text_under_cursor = true);
740 }
741
742 let cursor_at_pointer = galley.cursor_from_pos(
745 pointer_pos - inner_rect.min + state.text_offset + vec2(galley.rect.left(), 0.0),
746 );
747
748 if ui.visuals().text_cursor.preview
749 && response.hovered()
750 && ui.input(|i| i.pointer.is_moving())
751 {
752 let cursor_rect = TSTransform::from_translation(
754 inner_rect.min.to_vec2() - vec2(galley.rect.left(), 0.0),
755 ) * cursor_rect(&galley, &cursor_at_pointer, row_height);
756 text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect);
757 }
758
759 let is_being_dragged = ui.is_being_dragged(response.id);
760 let did_interact = state.cursor.pointer_interaction(
761 ui,
762 &response,
763 cursor_at_pointer,
764 &galley,
765 is_being_dragged,
766 );
767
768 if did_interact || response.clicked() {
769 ui.memory_mut(|mem| mem.request_focus(response.id));
770
771 state.last_interaction_time = ui.input(|i| i.time);
772 }
773 }
774
775 if interactive && response.hovered() {
776 ui.set_cursor_icon(CursorIcon::Text);
777 }
778
779 if text_changed {
780 response.mark_changed();
781 }
782
783 let mut galley_pos = align
784 .align_size_within_rect(galley.size(), inner_rect)
785 .intersect(inner_rect) .min;
787 let align_offset = inner_rect.left_top() - galley_pos;
788
789 if clip_text && align_offset.x == 0.0 {
791 let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
792 (Some(cursor_range), true) => galley.pos_from_cursor(cursor_range.primary).min.x,
793 _ => 0.0,
794 };
795
796 let mut offset_x = state.text_offset.x;
797 let visible_range = offset_x..=offset_x + inner_rect.width();
798
799 if !visible_range.contains(&cursor_pos) {
800 if cursor_pos < *visible_range.start() {
801 offset_x = cursor_pos;
802 } else {
803 offset_x = cursor_pos - inner_rect.width();
804 }
805 }
806
807 offset_x = offset_x
808 .at_most(galley.size().x - inner_rect.width())
809 .at_least(0.0);
810
811 state.text_offset = vec2(offset_x, align_offset.y);
812 galley_pos -= vec2(offset_x, 0.0);
813 } else {
814 state.text_offset = align_offset;
815 }
816
817 let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
818 (cursor_range, prev_cursor_range)
819 {
820 prev_cursor_range != cursor_range
821 } else {
822 false
823 };
824
825 if ui.is_rect_visible(inner_rect) {
826 let has_focus = ui.memory(|mem| mem.has_focus(id));
827
828 if has_focus && let Some(cursor_range) = state.cursor.range(&galley) {
829 paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None);
831 }
832
833 painter.galley(
834 galley_pos - vec2(galley.rect.left(), 0.0),
835 Arc::clone(&galley),
836 text_color,
837 );
838
839 if has_focus && let Some(cursor_range) = state.cursor.range(&galley) {
840 let primary_cursor_rect = cursor_rect(&galley, &cursor_range.primary, row_height)
841 .translate(galley_pos.to_vec2() - vec2(galley.rect.left(), 0.0));
842
843 if response.changed() || selection_changed {
844 ui.scroll_to_rect(primary_cursor_rect, None);
846 }
847
848 if text.is_mutable() && interactive {
849 let now = ui.input(|i| i.time);
850 if response.changed() || selection_changed {
851 state.last_interaction_time = now;
852 }
853
854 let viewport_has_focus = ui.input(|i| i.focused);
859 if viewport_has_focus {
860 text_selection::visuals::paint_text_cursor(
861 ui,
862 &painter,
863 primary_cursor_rect,
864 now - state.last_interaction_time,
865 );
866 }
867
868 let to_global = ui
870 .ctx()
871 .layer_transform_to_global(ui.layer_id())
872 .unwrap_or_default();
873
874 ui.output_mut(|o| {
875 o.ime = Some(crate::output::IMEOutput {
876 rect: to_global * inner_rect,
877 cursor_rect: to_global * primary_cursor_rect,
878 });
879 });
880 }
881 }
882 }
883
884 if state.ime_enabled && (response.gained_focus() || response.lost_focus()) {
886 state.ime_enabled = false;
887 if let Some(mut ccursor_range) = state.cursor.char_range() {
888 ccursor_range.secondary.index = ccursor_range.primary.index;
889 state.cursor.set_char_range(Some(ccursor_range));
890 }
891 ui.input_mut(|i| i.events.retain(|e| !matches!(e, Event::Ime(_))));
892 }
893
894 state.clone().store(ui.ctx(), id);
895
896 if response.changed() {
897 response.widget_info(|| {
898 WidgetInfo::text_edit(
899 ui.is_enabled(),
900 mask_if_password(password, prev_text.as_str()),
901 mask_if_password(password, text.as_str()),
902 hint_text_str.as_str(),
903 )
904 });
905 } else if selection_changed && let Some(cursor_range) = cursor_range {
906 let char_range = cursor_range.primary.index..=cursor_range.secondary.index;
907 let info = WidgetInfo::text_selection_changed(
908 ui.is_enabled(),
909 char_range,
910 mask_if_password(password, text.as_str()),
911 );
912 response.output_event(OutputEvent::TextSelectionChanged(info));
913 } else {
914 response.widget_info(|| {
915 WidgetInfo::text_edit(
916 ui.is_enabled(),
917 mask_if_password(password, prev_text.as_str()),
918 mask_if_password(password, text.as_str()),
919 hint_text_str.as_str(),
920 )
921 });
922 }
923
924 let role = if password {
925 accesskit::Role::PasswordInput
926 } else if multiline {
927 accesskit::Role::MultilineTextInput
928 } else {
929 accesskit::Role::TextInput
930 };
931
932 crate::text_selection::accesskit_text::update_accesskit_for_text_widget(
933 ui.ctx(),
934 id,
935 cursor_range,
936 role,
937 TSTransform::from_translation(galley_pos.to_vec2()),
938 &galley,
939 );
940
941 TextEditOutput {
942 response,
943 galley,
944 galley_pos,
945 text_clip_rect,
946 state,
947 cursor_range,
948 }
949 }
950}
951
952fn mask_if_password(is_password: bool, text: &str) -> String {
953 fn mask_password(text: &str) -> String {
954 std::iter::repeat_n(
955 epaint::text::PASSWORD_REPLACEMENT_CHAR,
956 text.chars().count(),
957 )
958 .collect::<String>()
959 }
960
961 if is_password {
962 mask_password(text)
963 } else {
964 text.to_owned()
965 }
966}
967
968#[expect(clippy::too_many_arguments)]
972fn events(
973 ui: &crate::Ui,
974 state: &mut TextEditState,
975 text: &mut dyn TextBuffer,
976 galley: &mut Arc<Galley>,
977 layouter: &mut dyn FnMut(&Ui, &dyn TextBuffer, f32) -> Arc<Galley>,
978 id: Id,
979 wrap_width: f32,
980 multiline: bool,
981 password: bool,
982 default_cursor_range: CCursorRange,
983 char_limit: usize,
984 event_filter: EventFilter,
985 return_key: Option<KeyboardShortcut>,
986) -> (bool, CCursorRange) {
987 let os = ui.os();
988
989 let mut cursor_range = state.cursor.range(galley).unwrap_or(default_cursor_range);
990
991 state.undoer.lock().feed_state(
994 ui.input(|i| i.time),
995 &(cursor_range, text.as_str().to_owned()),
996 );
997
998 let copy_if_not_password = |ui: &Ui, text: String| {
999 if !password {
1000 ui.copy_text(text);
1001 }
1002 };
1003
1004 let mut any_change = false;
1005
1006 let events = ui.input(|i| i.filtered_events(&event_filter));
1007
1008 for event in &events {
1009 let did_mutate_text = match event {
1010 event if cursor_range.on_event(os, event, galley, id) => None,
1012
1013 Event::Copy => {
1014 if !cursor_range.is_empty() {
1015 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
1016 }
1017 None
1018 }
1019 Event::Cut => {
1020 if cursor_range.is_empty() {
1021 None
1022 } else {
1023 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
1024 Some(CCursorRange::one(text.delete_selected(&cursor_range)))
1025 }
1026 }
1027 Event::Paste(text_to_insert) => {
1028 if !text_to_insert.is_empty() {
1029 let mut ccursor = text.delete_selected(&cursor_range);
1030 if multiline {
1031 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
1032 } else {
1033 let single_line = text_to_insert.replace(['\r', '\n'], " ");
1034 text.insert_text_at(&mut ccursor, &single_line, char_limit);
1035 }
1036
1037 Some(CCursorRange::one(ccursor))
1038 } else {
1039 None
1040 }
1041 }
1042 Event::Text(text_to_insert) => {
1043 if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
1045 let mut ccursor = text.delete_selected(&cursor_range);
1046
1047 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
1048
1049 Some(CCursorRange::one(ccursor))
1050 } else {
1051 None
1052 }
1053 }
1054 Event::Key {
1055 key: Key::Tab,
1056 pressed: true,
1057 modifiers,
1058 ..
1059 } if multiline => {
1060 let mut ccursor = text.delete_selected(&cursor_range);
1061 if modifiers.shift {
1062 text.decrease_indentation(&mut ccursor);
1064 } else {
1065 text.insert_text_at(&mut ccursor, "\t", char_limit);
1066 }
1067 Some(CCursorRange::one(ccursor))
1068 }
1069 Event::Key {
1070 key,
1071 pressed: true,
1072 modifiers,
1073 ..
1074 } if return_key.is_some_and(|return_key| {
1075 *key == return_key.logical_key && modifiers.matches_logically(return_key.modifiers)
1076 }) =>
1077 {
1078 if multiline {
1079 let mut ccursor = text.delete_selected(&cursor_range);
1080 text.insert_text_at(&mut ccursor, "\n", char_limit);
1081 Some(CCursorRange::one(ccursor))
1083 } else {
1084 ui.memory_mut(|mem| mem.surrender_focus(id)); break;
1086 }
1087 }
1088
1089 Event::Key {
1090 key,
1091 pressed: true,
1092 modifiers,
1093 ..
1094 } if (modifiers.matches_logically(Modifiers::COMMAND) && *key == Key::Y)
1095 || (modifiers.matches_logically(Modifiers::SHIFT | Modifiers::COMMAND)
1096 && *key == Key::Z) =>
1097 {
1098 if let Some((redo_ccursor_range, redo_txt)) = state
1099 .undoer
1100 .lock()
1101 .redo(&(cursor_range, text.as_str().to_owned()))
1102 {
1103 text.replace_with(redo_txt);
1104 Some(*redo_ccursor_range)
1105 } else {
1106 None
1107 }
1108 }
1109
1110 Event::Key {
1111 key: Key::Z,
1112 pressed: true,
1113 modifiers,
1114 ..
1115 } if modifiers.matches_logically(Modifiers::COMMAND) => {
1116 if let Some((undo_ccursor_range, undo_txt)) = state
1117 .undoer
1118 .lock()
1119 .undo(&(cursor_range, text.as_str().to_owned()))
1120 {
1121 text.replace_with(undo_txt);
1122 Some(*undo_ccursor_range)
1123 } else {
1124 None
1125 }
1126 }
1127
1128 Event::Key {
1129 modifiers,
1130 key,
1131 pressed: true,
1132 ..
1133 } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key),
1134
1135 Event::Ime(ime_event) => {
1136 fn clear_preedit_text(
1162 text: &mut dyn TextBuffer,
1163 preedit_range: &CCursorRange,
1164 ) -> CCursor {
1165 text.delete_selected(preedit_range)
1166 }
1167
1168 match ime_event {
1169 ImeEvent::Enabled => {
1170 state.ime_enabled = true;
1171 state.ime_cursor_range = cursor_range;
1172 None
1173 }
1174 ImeEvent::Preedit(preedit_text) => {
1175 if preedit_text == "\n" || preedit_text == "\r" {
1176 None
1177 } else {
1178 let mut ccursor = clear_preedit_text(text, &cursor_range);
1179
1180 let start_cursor = ccursor;
1181 if !preedit_text.is_empty() {
1182 text.insert_text_at(&mut ccursor, preedit_text, char_limit);
1183 }
1184 state.ime_cursor_range = cursor_range;
1185 Some(CCursorRange::two(start_cursor, ccursor))
1186 }
1187 }
1188 ImeEvent::Commit(commit_text) => {
1189 if commit_text == "\n" || commit_text == "\r" {
1190 None
1191 } else {
1192 state.ime_enabled = false;
1193
1194 let mut ccursor = clear_preedit_text(text, &cursor_range);
1195
1196 if !commit_text.is_empty()
1197 && cursor_range.secondary.index
1198 == state.ime_cursor_range.secondary.index
1199 {
1200 text.insert_text_at(&mut ccursor, commit_text, char_limit);
1201 }
1202
1203 Some(CCursorRange::one(ccursor))
1204 }
1205 }
1206 ImeEvent::Disabled => {
1207 state.ime_enabled = false;
1208 None
1209 }
1210 }
1211 }
1212
1213 _ => None,
1214 };
1215
1216 if let Some(new_ccursor_range) = did_mutate_text {
1217 any_change = true;
1218
1219 *galley = layouter(ui, text, wrap_width);
1221
1222 cursor_range = new_ccursor_range;
1224 }
1225 }
1226
1227 state.cursor.set_char_range(Some(cursor_range));
1228
1229 state.undoer.lock().feed_state(
1230 ui.input(|i| i.time),
1231 &(cursor_range, text.as_str().to_owned()),
1232 );
1233
1234 (any_change, cursor_range)
1235}
1236
1237fn check_for_mutating_key_press(
1241 os: OperatingSystem,
1242 cursor_range: &CCursorRange,
1243 text: &mut dyn TextBuffer,
1244 galley: &Galley,
1245 modifiers: &Modifiers,
1246 key: Key,
1247) -> Option<CCursorRange> {
1248 match key {
1249 Key::Backspace => {
1250 let ccursor = if modifiers.mac_cmd {
1251 text.delete_paragraph_before_cursor(galley, cursor_range)
1252 } else if let Some(cursor) = cursor_range.single() {
1253 if modifiers.alt || modifiers.ctrl {
1254 text.delete_previous_word(cursor)
1256 } else {
1257 text.delete_previous_char(cursor)
1258 }
1259 } else {
1260 text.delete_selected(cursor_range)
1261 };
1262 Some(CCursorRange::one(ccursor))
1263 }
1264
1265 Key::Delete if !modifiers.shift || os != OperatingSystem::Windows => {
1266 let ccursor = if modifiers.mac_cmd {
1267 text.delete_paragraph_after_cursor(galley, cursor_range)
1268 } else if let Some(cursor) = cursor_range.single() {
1269 if modifiers.alt || modifiers.ctrl {
1270 text.delete_next_word(cursor)
1272 } else {
1273 text.delete_next_char(cursor)
1274 }
1275 } else {
1276 text.delete_selected(cursor_range)
1277 };
1278 let ccursor = CCursor {
1279 prefer_next_row: true,
1280 ..ccursor
1281 };
1282 Some(CCursorRange::one(ccursor))
1283 }
1284
1285 Key::H if modifiers.ctrl => {
1286 let ccursor = text.delete_previous_char(cursor_range.primary);
1287 Some(CCursorRange::one(ccursor))
1288 }
1289
1290 Key::K if modifiers.ctrl => {
1291 let ccursor = text.delete_paragraph_after_cursor(galley, cursor_range);
1292 Some(CCursorRange::one(ccursor))
1293 }
1294
1295 Key::U if modifiers.ctrl => {
1296 let ccursor = text.delete_paragraph_before_cursor(galley, cursor_range);
1297 Some(CCursorRange::one(ccursor))
1298 }
1299
1300 Key::W if modifiers.ctrl => {
1301 let ccursor = if let Some(cursor) = cursor_range.single() {
1302 text.delete_previous_word(cursor)
1303 } else {
1304 text.delete_selected(cursor_range)
1305 };
1306 Some(CCursorRange::one(ccursor))
1307 }
1308
1309 _ => None,
1310 }
1311}