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