1use std::sync::Arc;
2
3use emath::Rect;
4use epaint::text::{cursor::CCursor, Galley, LayoutJob};
5
6use crate::{
7 epaint,
8 os::OperatingSystem,
9 output::OutputEvent,
10 text_selection,
11 text_selection::{
12 text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange, CursorRange,
13 },
14 vec2, Align, Align2, Color32, Context, CursorIcon, Event, EventFilter, FontSelection, Id,
15 ImeEvent, Key, KeyboardShortcut, Margin, Modifiers, NumExt, Response, Sense, Shape, TextBuffer,
16 TextStyle, TextWrapMode, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetWithState,
17};
18
19use super::{TextEditOutput, TextEditState};
20
21#[must_use = "You should put this widget in a ui with `ui.add(widget);`"]
65pub struct TextEdit<'t> {
66 text: &'t mut dyn TextBuffer,
67 hint_text: WidgetText,
68 hint_text_font: Option<FontSelection>,
69 id: Option<Id>,
70 id_salt: Option<Id>,
71 font_selection: FontSelection,
72 text_color: Option<Color32>,
73 layouter: Option<&'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>>,
74 password: bool,
75 frame: bool,
76 margin: Margin,
77 multiline: bool,
78 interactive: bool,
79 desired_width: Option<f32>,
80 desired_height_rows: usize,
81 event_filter: EventFilter,
82 cursor_at_end: bool,
83 min_size: Vec2,
84 align: Align2,
85 clip_text: bool,
86 char_limit: usize,
87 return_key: Option<KeyboardShortcut>,
88 background_color: Option<Color32>,
89}
90
91impl<'t> WidgetWithState for TextEdit<'t> {
92 type State = TextEditState;
93}
94
95impl<'t> TextEdit<'t> {
96 pub fn load_state(ctx: &Context, id: Id) -> Option<TextEditState> {
97 TextEditState::load(ctx, id)
98 }
99
100 pub fn store_state(ctx: &Context, id: Id, state: TextEditState) {
101 state.store(ctx, id);
102 }
103}
104
105impl<'t> TextEdit<'t> {
106 pub fn singleline(text: &'t mut dyn TextBuffer) -> Self {
108 Self {
109 desired_height_rows: 1,
110 multiline: false,
111 clip_text: true,
112 ..Self::multiline(text)
113 }
114 }
115
116 pub fn multiline(text: &'t mut dyn TextBuffer) -> Self {
118 Self {
119 text,
120 hint_text: Default::default(),
121 hint_text_font: None,
122 id: None,
123 id_salt: None,
124 font_selection: Default::default(),
125 text_color: None,
126 layouter: None,
127 password: false,
128 frame: true,
129 margin: Margin::symmetric(4.0, 2.0),
130 multiline: true,
131 interactive: true,
132 desired_width: None,
133 desired_height_rows: 4,
134 event_filter: EventFilter {
135 horizontal_arrows: true,
137 vertical_arrows: true,
138 tab: false, ..Default::default()
140 },
141 cursor_at_end: true,
142 min_size: Vec2::ZERO,
143 align: Align2::LEFT_TOP,
144 clip_text: false,
145 char_limit: usize::MAX,
146 return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)),
147 background_color: None,
148 }
149 }
150
151 pub fn code_editor(self) -> Self {
156 self.font(TextStyle::Monospace).lock_focus(true)
157 }
158
159 #[inline]
161 pub fn id(mut self, id: Id) -> Self {
162 self.id = Some(id);
163 self
164 }
165
166 #[inline]
168 pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
169 self.id_salt(id_salt)
170 }
171
172 #[inline]
174 pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
175 self.id_salt = Some(Id::new(id_salt));
176 self
177 }
178
179 #[inline]
202 pub fn hint_text(mut self, hint_text: impl Into<WidgetText>) -> Self {
203 self.hint_text = hint_text.into();
204 self
205 }
206
207 #[inline]
210 pub fn background_color(mut self, color: Color32) -> Self {
211 self.background_color = Some(color);
212 self
213 }
214
215 #[inline]
217 pub fn hint_text_font(mut self, hint_text_font: impl Into<FontSelection>) -> Self {
218 self.hint_text_font = Some(hint_text_font.into());
219 self
220 }
221
222 #[inline]
224 pub fn password(mut self, password: bool) -> Self {
225 self.password = password;
226 self
227 }
228
229 #[inline]
231 pub fn font(mut self, font_selection: impl Into<FontSelection>) -> Self {
232 self.font_selection = font_selection.into();
233 self
234 }
235
236 #[inline]
237 pub fn text_color(mut self, text_color: Color32) -> Self {
238 self.text_color = Some(text_color);
239 self
240 }
241
242 #[inline]
243 pub fn text_color_opt(mut self, text_color: Option<Color32>) -> Self {
244 self.text_color = text_color;
245 self
246 }
247
248 #[inline]
272 pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>) -> Self {
273 self.layouter = Some(layouter);
274
275 self
276 }
277
278 #[inline]
282 pub fn interactive(mut self, interactive: bool) -> Self {
283 self.interactive = interactive;
284 self
285 }
286
287 #[inline]
289 pub fn frame(mut self, frame: bool) -> Self {
290 self.frame = frame;
291 self
292 }
293
294 #[inline]
296 pub fn margin(mut self, margin: impl Into<Margin>) -> Self {
297 self.margin = margin.into();
298 self
299 }
300
301 #[inline]
304 pub fn desired_width(mut self, desired_width: f32) -> Self {
305 self.desired_width = Some(desired_width);
306 self
307 }
308
309 #[inline]
313 pub fn desired_rows(mut self, desired_height_rows: usize) -> Self {
314 self.desired_height_rows = desired_height_rows;
315 self
316 }
317
318 #[inline]
324 pub fn lock_focus(mut self, tab_will_indent: bool) -> Self {
325 self.event_filter.tab = tab_will_indent;
326 self
327 }
328
329 #[inline]
333 pub fn cursor_at_end(mut self, b: bool) -> Self {
334 self.cursor_at_end = b;
335 self
336 }
337
338 #[inline]
344 pub fn clip_text(mut self, b: bool) -> Self {
345 if !self.multiline {
347 self.clip_text = b;
348 }
349 self
350 }
351
352 #[inline]
356 pub fn char_limit(mut self, limit: usize) -> Self {
357 self.char_limit = limit;
358 self
359 }
360
361 #[inline]
363 pub fn horizontal_align(mut self, align: Align) -> Self {
364 self.align.0[0] = align;
365 self
366 }
367
368 #[inline]
370 pub fn vertical_align(mut self, align: Align) -> Self {
371 self.align.0[1] = align;
372 self
373 }
374
375 #[inline]
377 pub fn min_size(mut self, min_size: Vec2) -> Self {
378 self.min_size = min_size;
379 self
380 }
381
382 #[inline]
389 pub fn return_key(mut self, return_key: impl Into<Option<KeyboardShortcut>>) -> Self {
390 self.return_key = return_key.into();
391 self
392 }
393}
394
395impl<'t> Widget for TextEdit<'t> {
398 fn ui(self, ui: &mut Ui) -> Response {
399 self.show(ui).response
400 }
401}
402
403impl<'t> TextEdit<'t> {
404 pub fn show(self, ui: &mut Ui) -> TextEditOutput {
420 let is_mutable = self.text.is_mutable();
421 let frame = self.frame;
422 let where_to_put_background = ui.painter().add(Shape::Noop);
423 let background_color = self
424 .background_color
425 .unwrap_or(ui.visuals().extreme_bg_color);
426 let margin = self.margin;
427 let mut output = self.show_content(ui);
428
429 let outer_rect = output.response.rect;
432 let inner_rect = outer_rect - margin;
433 output.response.rect = inner_rect;
434
435 if frame {
436 let visuals = ui.style().interact(&output.response);
437 let frame_rect = outer_rect.expand(visuals.expansion);
438 let shape = if is_mutable {
439 if output.response.has_focus() {
440 epaint::RectShape::new(
441 frame_rect,
442 visuals.rounding,
443 background_color,
444 ui.visuals().selection.stroke,
445 )
446 } else {
447 epaint::RectShape::new(
448 frame_rect,
449 visuals.rounding,
450 background_color,
451 visuals.bg_stroke, )
453 }
454 } else {
455 let visuals = &ui.style().visuals.widgets.inactive;
456 epaint::RectShape::stroke(
457 frame_rect,
458 visuals.rounding,
459 visuals.bg_stroke, )
461 };
462
463 ui.painter().set(where_to_put_background, shape);
464 }
465
466 output
467 }
468
469 fn show_content(self, ui: &mut Ui) -> TextEditOutput {
470 let TextEdit {
471 text,
472 hint_text,
473 hint_text_font,
474 id,
475 id_salt,
476 font_selection,
477 text_color,
478 layouter,
479 password,
480 frame: _,
481 margin,
482 multiline,
483 interactive,
484 desired_width,
485 desired_height_rows,
486 event_filter,
487 cursor_at_end,
488 min_size,
489 align,
490 clip_text,
491 char_limit,
492 return_key,
493 background_color: _,
494 } = self;
495
496 let text_color = text_color
497 .or(ui.visuals().override_text_color)
498 .unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
500
501 let prev_text = text.as_str().to_owned();
502
503 let font_id = font_selection.resolve(ui.style());
504 let row_height = ui.fonts(|f| f.row_height(&font_id));
505 const MIN_WIDTH: f32 = 24.0; let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH);
507 let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
508 let wrap_width = if ui.layout().horizontal_justify() {
509 available_width
510 } else {
511 desired_width.min(available_width)
512 };
513
514 let font_id_clone = font_id.clone();
515 let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
516 let text = mask_if_password(password, text);
517 let layout_job = if multiline {
518 LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width)
519 } else {
520 LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color)
521 };
522 ui.fonts(|f| f.layout_job(layout_job))
523 };
524
525 let layouter = layouter.unwrap_or(&mut default_layouter);
526
527 let mut galley = layouter(ui, text.as_str(), wrap_width);
528
529 let desired_inner_width = if clip_text {
530 wrap_width } else {
532 galley.size().x.max(wrap_width)
533 };
534 let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
535 let desired_inner_size = vec2(desired_inner_width, galley.size().y.max(desired_height));
536 let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size);
537 let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size);
538 let rect = outer_rect - margin; let id = id.unwrap_or_else(|| {
541 if let Some(id_salt) = id_salt {
542 ui.make_persistent_id(id_salt)
543 } else {
544 auto_id }
546 });
547 let mut state = TextEditState::load(ui.ctx(), id).unwrap_or_default();
548
549 let allow_drag_to_select =
554 ui.input(|i| !i.has_touch_screen()) || ui.memory(|mem| mem.has_focus(id));
555
556 let sense = if interactive {
557 if allow_drag_to_select {
558 Sense::click_and_drag()
559 } else {
560 Sense::click()
561 }
562 } else {
563 Sense::hover()
564 };
565 let mut response = ui.interact(outer_rect, id, sense);
566 response.intrinsic_size = Some(Vec2::new(desired_width, desired_outer_size.y));
567
568 response.fake_primary_click = false; let text_clip_rect = rect;
571 let painter = ui.painter_at(text_clip_rect.expand(1.0)); if interactive {
574 if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
575 if response.hovered() && text.is_mutable() {
576 ui.output_mut(|o| o.mutable_text_under_cursor = true);
577 }
578
579 let singleline_offset = vec2(state.singleline_offset, 0.0);
582 let cursor_at_pointer =
583 galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset);
584
585 if ui.visuals().text_cursor.preview
586 && response.hovered()
587 && ui.input(|i| i.pointer.is_moving())
588 {
589 let cursor_rect =
591 cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height);
592 text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect);
593 }
594
595 let is_being_dragged = ui.ctx().is_being_dragged(response.id);
596 let did_interact = state.cursor.pointer_interaction(
597 ui,
598 &response,
599 cursor_at_pointer,
600 &galley,
601 is_being_dragged,
602 );
603
604 if did_interact || response.clicked() {
605 ui.memory_mut(|mem| mem.request_focus(response.id));
606
607 state.last_interaction_time = ui.ctx().input(|i| i.time);
608 }
609 }
610 }
611
612 if interactive && response.hovered() {
613 ui.ctx().set_cursor_icon(CursorIcon::Text);
614 }
615
616 let mut cursor_range = None;
617 let prev_cursor_range = state.cursor.range(&galley);
618 if interactive && ui.memory(|mem| mem.has_focus(id)) {
619 ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter));
620
621 let default_cursor_range = if cursor_at_end {
622 CursorRange::one(galley.end())
623 } else {
624 CursorRange::default()
625 };
626
627 let (changed, new_cursor_range) = events(
628 ui,
629 &mut state,
630 text,
631 &mut galley,
632 layouter,
633 id,
634 wrap_width,
635 multiline,
636 password,
637 default_cursor_range,
638 char_limit,
639 event_filter,
640 return_key,
641 );
642
643 if changed {
644 response.mark_changed();
645 }
646 cursor_range = Some(new_cursor_range);
647 }
648
649 let mut galley_pos = align
650 .align_size_within_rect(galley.size(), rect)
651 .intersect(rect) .min;
653 let align_offset = rect.left() - galley_pos.x;
654
655 if clip_text && align_offset == 0.0 {
657 let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
658 (Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
659 _ => 0.0,
660 };
661
662 let mut offset_x = state.singleline_offset;
663 let visible_range = offset_x..=offset_x + desired_inner_size.x;
664
665 if !visible_range.contains(&cursor_pos) {
666 if cursor_pos < *visible_range.start() {
667 offset_x = cursor_pos;
668 } else {
669 offset_x = cursor_pos - desired_inner_size.x;
670 }
671 }
672
673 offset_x = offset_x
674 .at_most(galley.size().x - desired_inner_size.x)
675 .at_least(0.0);
676
677 state.singleline_offset = offset_x;
678 galley_pos -= vec2(offset_x, 0.0);
679 } else {
680 state.singleline_offset = align_offset;
681 }
682
683 let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
684 (cursor_range, prev_cursor_range)
685 {
686 prev_cursor_range.as_ccursor_range() != cursor_range.as_ccursor_range()
687 } else {
688 false
689 };
690
691 if ui.is_rect_visible(rect) {
692 if text.as_str().is_empty() && !hint_text.is_empty() {
693 let hint_text_color = ui.visuals().weak_text_color();
694 let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
695 let galley = if multiline {
696 hint_text.into_galley(
697 ui,
698 Some(TextWrapMode::Wrap),
699 desired_inner_size.x,
700 hint_text_font_id,
701 )
702 } else {
703 hint_text.into_galley(
704 ui,
705 Some(TextWrapMode::Extend),
706 f32::INFINITY,
707 hint_text_font_id,
708 )
709 };
710 let galley_pos = align
711 .align_size_within_rect(galley.size(), rect)
712 .intersect(rect)
713 .min;
714 painter.galley(galley_pos, galley, hint_text_color);
715 }
716
717 let has_focus = ui.memory(|mem| mem.has_focus(id));
718
719 if has_focus {
720 if let Some(cursor_range) = state.cursor.range(&galley) {
721 paint_text_selection(&mut galley, ui.visuals(), &cursor_range, None);
723 }
724 }
725
726 let extra_size = galley.size() - rect.size();
729 if extra_size.x > 0.0 || extra_size.y > 0.0 {
730 ui.allocate_rect(
731 Rect::from_min_size(outer_rect.max, extra_size),
732 Sense::hover(),
733 );
734 }
735
736 painter.galley(galley_pos, galley.clone(), text_color);
737
738 if has_focus {
739 if let Some(cursor_range) = state.cursor.range(&galley) {
740 let primary_cursor_rect =
741 cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height);
742
743 if response.changed || selection_changed {
744 ui.scroll_to_rect(primary_cursor_rect + margin, None);
746 }
747
748 if text.is_mutable() && interactive {
749 let now = ui.ctx().input(|i| i.time);
750 if response.changed || selection_changed {
751 state.last_interaction_time = now;
752 }
753
754 let viewport_has_focus = ui.ctx().input(|i| i.focused);
759 if viewport_has_focus {
760 text_selection::visuals::paint_text_cursor(
761 ui,
762 &painter,
763 primary_cursor_rect,
764 now - state.last_interaction_time,
765 );
766 }
767
768 let to_global = ui
770 .ctx()
771 .layer_transform_to_global(ui.layer_id())
772 .unwrap_or_default();
773
774 ui.ctx().output_mut(|o| {
775 o.ime = Some(crate::output::IMEOutput {
776 rect: to_global * rect,
777 cursor_rect: to_global * primary_cursor_rect,
778 });
779 });
780 }
781 }
782 }
783 }
784
785 if state.ime_enabled && (response.gained_focus() || response.lost_focus()) {
787 state.ime_enabled = false;
788 if let Some(mut ccursor_range) = state.cursor.char_range() {
789 ccursor_range.secondary.index = ccursor_range.primary.index;
790 state.cursor.set_char_range(Some(ccursor_range));
791 }
792 ui.input_mut(|i| i.events.retain(|e| !matches!(e, Event::Ime(_))));
793 }
794
795 state.clone().store(ui.ctx(), id);
796
797 if response.changed {
798 response.widget_info(|| {
799 WidgetInfo::text_edit(
800 ui.is_enabled(),
801 mask_if_password(password, prev_text.as_str()),
802 mask_if_password(password, text.as_str()),
803 )
804 });
805 } else if selection_changed {
806 let cursor_range = cursor_range.unwrap();
807 let char_range =
808 cursor_range.primary.ccursor.index..=cursor_range.secondary.ccursor.index;
809 let info = WidgetInfo::text_selection_changed(
810 ui.is_enabled(),
811 char_range,
812 mask_if_password(password, text.as_str()),
813 );
814 response.output_event(OutputEvent::TextSelectionChanged(info));
815 } else {
816 response.widget_info(|| {
817 WidgetInfo::text_edit(
818 ui.is_enabled(),
819 mask_if_password(password, prev_text.as_str()),
820 mask_if_password(password, text.as_str()),
821 )
822 });
823 }
824
825 #[cfg(feature = "accesskit")]
826 {
827 let role = if password {
828 accesskit::Role::PasswordInput
829 } else if multiline {
830 accesskit::Role::MultilineTextInput
831 } else {
832 accesskit::Role::TextInput
833 };
834
835 crate::text_selection::accesskit_text::update_accesskit_for_text_widget(
836 ui.ctx(),
837 id,
838 cursor_range,
839 role,
840 galley_pos,
841 &galley,
842 );
843 }
844
845 TextEditOutput {
846 response,
847 galley,
848 galley_pos,
849 text_clip_rect,
850 state,
851 cursor_range,
852 }
853 }
854}
855
856fn mask_if_password(is_password: bool, text: &str) -> String {
857 fn mask_password(text: &str) -> String {
858 std::iter::repeat(epaint::text::PASSWORD_REPLACEMENT_CHAR)
859 .take(text.chars().count())
860 .collect::<String>()
861 }
862
863 if is_password {
864 mask_password(text)
865 } else {
866 text.to_owned()
867 }
868}
869
870#[allow(clippy::too_many_arguments)]
874fn events(
875 ui: &crate::Ui,
876 state: &mut TextEditState,
877 text: &mut dyn TextBuffer,
878 galley: &mut Arc<Galley>,
879 layouter: &mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>,
880 id: Id,
881 wrap_width: f32,
882 multiline: bool,
883 password: bool,
884 default_cursor_range: CursorRange,
885 char_limit: usize,
886 event_filter: EventFilter,
887 return_key: Option<KeyboardShortcut>,
888) -> (bool, CursorRange) {
889 let os = ui.ctx().os();
890
891 let mut cursor_range = state.cursor.range(galley).unwrap_or(default_cursor_range);
892
893 state.undoer.lock().feed_state(
896 ui.input(|i| i.time),
897 &(cursor_range.as_ccursor_range(), text.as_str().to_owned()),
898 );
899
900 let copy_if_not_password = |ui: &Ui, text: String| {
901 if !password {
902 ui.ctx().copy_text(text);
903 }
904 };
905
906 let mut any_change = false;
907
908 let mut events = ui.input(|i| i.filtered_events(&event_filter));
909
910 if state.ime_enabled {
911 remove_ime_incompatible_events(&mut events);
912 events.sort_by_key(|e| !matches!(e, Event::Ime(_)));
914 }
915
916 for event in &events {
917 let did_mutate_text = match event {
918 event if cursor_range.on_event(os, event, galley, id) => None,
920
921 Event::Copy => {
922 if cursor_range.is_empty() {
923 None
924 } else {
925 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
926 None
927 }
928 }
929 Event::Cut => {
930 if cursor_range.is_empty() {
931 None
932 } else {
933 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
934 Some(CCursorRange::one(text.delete_selected(&cursor_range)))
935 }
936 }
937 Event::Paste(text_to_insert) => {
938 if !text_to_insert.is_empty() {
939 let mut ccursor = text.delete_selected(&cursor_range);
940
941 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
942
943 Some(CCursorRange::one(ccursor))
944 } else {
945 None
946 }
947 }
948 Event::Text(text_to_insert) => {
949 if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
951 let mut ccursor = text.delete_selected(&cursor_range);
952
953 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
954
955 Some(CCursorRange::one(ccursor))
956 } else {
957 None
958 }
959 }
960 Event::Key {
961 key: Key::Tab,
962 pressed: true,
963 modifiers,
964 ..
965 } if multiline => {
966 let mut ccursor = text.delete_selected(&cursor_range);
967 if modifiers.shift {
968 text.decrease_indentation(&mut ccursor);
970 } else {
971 text.insert_text_at(&mut ccursor, "\t", char_limit);
972 }
973 Some(CCursorRange::one(ccursor))
974 }
975 Event::Key {
976 key,
977 pressed: true,
978 modifiers,
979 ..
980 } if return_key.is_some_and(|return_key| {
981 *key == return_key.logical_key && modifiers.matches_logically(return_key.modifiers)
982 }) =>
983 {
984 if multiline {
985 let mut ccursor = text.delete_selected(&cursor_range);
986 text.insert_text_at(&mut ccursor, "\n", char_limit);
987 Some(CCursorRange::one(ccursor))
989 } else {
990 ui.memory_mut(|mem| mem.surrender_focus(id)); break;
992 }
993 }
994
995 Event::Key {
996 key,
997 pressed: true,
998 modifiers,
999 ..
1000 } if (modifiers.matches_logically(Modifiers::COMMAND) && *key == Key::Y)
1001 || (modifiers.matches_logically(Modifiers::SHIFT | Modifiers::COMMAND)
1002 && *key == Key::Z) =>
1003 {
1004 if let Some((redo_ccursor_range, redo_txt)) = state
1005 .undoer
1006 .lock()
1007 .redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
1008 {
1009 text.replace_with(redo_txt);
1010 Some(*redo_ccursor_range)
1011 } else {
1012 None
1013 }
1014 }
1015
1016 Event::Key {
1017 key: Key::Z,
1018 pressed: true,
1019 modifiers,
1020 ..
1021 } if modifiers.matches_logically(Modifiers::COMMAND) => {
1022 if let Some((undo_ccursor_range, undo_txt)) = state
1023 .undoer
1024 .lock()
1025 .undo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
1026 {
1027 text.replace_with(undo_txt);
1028 Some(*undo_ccursor_range)
1029 } else {
1030 None
1031 }
1032 }
1033
1034 Event::Key {
1035 modifiers,
1036 key,
1037 pressed: true,
1038 ..
1039 } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key),
1040
1041 Event::Ime(ime_event) => match ime_event {
1042 ImeEvent::Enabled => {
1043 state.ime_enabled = true;
1044 state.ime_cursor_range = cursor_range;
1045 None
1046 }
1047 ImeEvent::Preedit(text_mark) => {
1048 if text_mark == "\n" || text_mark == "\r" {
1049 None
1050 } else {
1051 let mut ccursor = text.delete_selected(&cursor_range);
1054 let start_cursor = ccursor;
1055 if !text_mark.is_empty() {
1056 text.insert_text_at(&mut ccursor, text_mark, char_limit);
1057 }
1058 state.ime_cursor_range = cursor_range;
1059 Some(CCursorRange::two(start_cursor, ccursor))
1060 }
1061 }
1062 ImeEvent::Commit(prediction) => {
1063 if prediction == "\n" || prediction == "\r" {
1064 None
1065 } else {
1066 state.ime_enabled = false;
1067
1068 if !prediction.is_empty()
1069 && cursor_range.secondary.ccursor.index
1070 == state.ime_cursor_range.secondary.ccursor.index
1071 {
1072 let mut ccursor = text.delete_selected(&cursor_range);
1073 text.insert_text_at(&mut ccursor, prediction, char_limit);
1074 Some(CCursorRange::one(ccursor))
1075 } else {
1076 let ccursor = cursor_range.primary.ccursor;
1077 Some(CCursorRange::one(ccursor))
1078 }
1079 }
1080 }
1081 ImeEvent::Disabled => {
1082 state.ime_enabled = false;
1083 None
1084 }
1085 },
1086
1087 _ => None,
1088 };
1089
1090 if let Some(new_ccursor_range) = did_mutate_text {
1091 any_change = true;
1092
1093 *galley = layouter(ui, text.as_str(), wrap_width);
1095
1096 cursor_range = CursorRange {
1098 primary: galley.from_ccursor(new_ccursor_range.primary),
1099 secondary: galley.from_ccursor(new_ccursor_range.secondary),
1100 };
1101 }
1102 }
1103
1104 state.cursor.set_range(Some(cursor_range));
1105
1106 state.undoer.lock().feed_state(
1107 ui.input(|i| i.time),
1108 &(cursor_range.as_ccursor_range(), text.as_str().to_owned()),
1109 );
1110
1111 (any_change, cursor_range)
1112}
1113
1114fn remove_ime_incompatible_events(events: &mut Vec<Event>) {
1117 events.retain(|event| {
1120 !matches!(
1121 event,
1122 Event::Key { repeat: true, .. }
1123 | Event::Key {
1124 key: Key::Backspace
1125 | Key::ArrowUp
1126 | Key::ArrowDown
1127 | Key::ArrowLeft
1128 | Key::ArrowRight,
1129 ..
1130 }
1131 )
1132 });
1133}
1134
1135fn check_for_mutating_key_press(
1139 os: OperatingSystem,
1140 cursor_range: &CursorRange,
1141 text: &mut dyn TextBuffer,
1142 galley: &Galley,
1143 modifiers: &Modifiers,
1144 key: Key,
1145) -> Option<CCursorRange> {
1146 match key {
1147 Key::Backspace => {
1148 let ccursor = if modifiers.mac_cmd {
1149 text.delete_paragraph_before_cursor(galley, cursor_range)
1150 } else if let Some(cursor) = cursor_range.single() {
1151 if modifiers.alt || modifiers.ctrl {
1152 text.delete_previous_word(cursor.ccursor)
1154 } else {
1155 text.delete_previous_char(cursor.ccursor)
1156 }
1157 } else {
1158 text.delete_selected(cursor_range)
1159 };
1160 Some(CCursorRange::one(ccursor))
1161 }
1162
1163 Key::Delete if !modifiers.shift || os != OperatingSystem::Windows => {
1164 let ccursor = if modifiers.mac_cmd {
1165 text.delete_paragraph_after_cursor(galley, cursor_range)
1166 } else if let Some(cursor) = cursor_range.single() {
1167 if modifiers.alt || modifiers.ctrl {
1168 text.delete_next_word(cursor.ccursor)
1170 } else {
1171 text.delete_next_char(cursor.ccursor)
1172 }
1173 } else {
1174 text.delete_selected(cursor_range)
1175 };
1176 let ccursor = CCursor {
1177 prefer_next_row: true,
1178 ..ccursor
1179 };
1180 Some(CCursorRange::one(ccursor))
1181 }
1182
1183 Key::H if modifiers.ctrl => {
1184 let ccursor = text.delete_previous_char(cursor_range.primary.ccursor);
1185 Some(CCursorRange::one(ccursor))
1186 }
1187
1188 Key::K if modifiers.ctrl => {
1189 let ccursor = text.delete_paragraph_after_cursor(galley, cursor_range);
1190 Some(CCursorRange::one(ccursor))
1191 }
1192
1193 Key::U if modifiers.ctrl => {
1194 let ccursor = text.delete_paragraph_before_cursor(galley, cursor_range);
1195 Some(CCursorRange::one(ccursor))
1196 }
1197
1198 Key::W if modifiers.ctrl => {
1199 let ccursor = if let Some(cursor) = cursor_range.single() {
1200 text.delete_previous_word(cursor.ccursor)
1201 } else {
1202 text.delete_selected(cursor_range)
1203 };
1204 Some(CCursorRange::one(ccursor))
1205 }
1206
1207 _ => None,
1208 }
1209}