1use std::sync::Arc;
2
3use epaint::text::{cursor::*, Galley, LayoutJob};
4
5use crate::{
6 os::OperatingSystem,
7 output::OutputEvent,
8 text_selection::{
9 text_cursor_state::cursor_rect, visuals::paint_text_selection, CCursorRange, CursorRange,
10 },
11 *,
12};
13
14use super::{TextEditOutput, TextEditState};
15
16#[must_use = "You should put this widget in an ui with `ui.add(widget);`"]
60pub struct TextEdit<'t> {
61 text: &'t mut dyn TextBuffer,
62 hint_text: WidgetText,
63 hint_text_font: Option<FontSelection>,
64 id: Option<Id>,
65 id_source: Option<Id>,
66 font_selection: FontSelection,
67 text_color: Option<Color32>,
68 layouter: Option<&'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>>,
69 password: bool,
70 frame: bool,
71 margin: Margin,
72 multiline: bool,
73 interactive: bool,
74 desired_width: Option<f32>,
75 desired_height_rows: usize,
76 event_filter: EventFilter,
77 cursor_at_end: bool,
78 min_size: Vec2,
79 align: Align2,
80 clip_text: bool,
81 char_limit: usize,
82 return_key: Option<KeyboardShortcut>,
83}
84
85impl<'t> WidgetWithState for TextEdit<'t> {
86 type State = TextEditState;
87}
88
89impl<'t> TextEdit<'t> {
90 pub fn load_state(ctx: &Context, id: Id) -> Option<TextEditState> {
91 TextEditState::load(ctx, id)
92 }
93
94 pub fn store_state(ctx: &Context, id: Id, state: TextEditState) {
95 state.store(ctx, id);
96 }
97}
98
99impl<'t> TextEdit<'t> {
100 pub fn singleline(text: &'t mut dyn TextBuffer) -> Self {
102 Self {
103 desired_height_rows: 1,
104 multiline: false,
105 clip_text: true,
106 ..Self::multiline(text)
107 }
108 }
109
110 pub fn multiline(text: &'t mut dyn TextBuffer) -> Self {
112 Self {
113 text,
114 hint_text: Default::default(),
115 hint_text_font: None,
116 id: None,
117 id_source: None,
118 font_selection: Default::default(),
119 text_color: None,
120 layouter: None,
121 password: false,
122 frame: true,
123 margin: Margin::symmetric(4.0, 2.0),
124 multiline: true,
125 interactive: true,
126 desired_width: None,
127 desired_height_rows: 4,
128 event_filter: EventFilter {
129 horizontal_arrows: true,
131 vertical_arrows: true,
132 tab: false, ..Default::default()
134 },
135 cursor_at_end: true,
136 min_size: Vec2::ZERO,
137 align: Align2::LEFT_TOP,
138 clip_text: false,
139 char_limit: usize::MAX,
140 return_key: Some(KeyboardShortcut::new(Modifiers::NONE, Key::Enter)),
141 }
142 }
143
144 pub fn code_editor(self) -> Self {
149 self.font(TextStyle::Monospace).lock_focus(true)
150 }
151
152 #[inline]
154 pub fn id(mut self, id: Id) -> Self {
155 self.id = Some(id);
156 self
157 }
158
159 #[inline]
161 pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
162 self.id_source = Some(Id::new(id_source));
163 self
164 }
165
166 #[inline]
189 pub fn hint_text(mut self, hint_text: impl Into<WidgetText>) -> Self {
190 self.hint_text = hint_text.into();
191 self
192 }
193
194 #[inline]
196 pub fn hint_text_font(mut self, hint_text_font: impl Into<FontSelection>) -> Self {
197 self.hint_text_font = Some(hint_text_font.into());
198 self
199 }
200
201 #[inline]
203 pub fn password(mut self, password: bool) -> Self {
204 self.password = password;
205 self
206 }
207
208 #[inline]
210 pub fn font(mut self, font_selection: impl Into<FontSelection>) -> Self {
211 self.font_selection = font_selection.into();
212 self
213 }
214
215 #[inline]
216 pub fn text_color(mut self, text_color: Color32) -> Self {
217 self.text_color = Some(text_color);
218 self
219 }
220
221 #[inline]
222 pub fn text_color_opt(mut self, text_color: Option<Color32>) -> Self {
223 self.text_color = text_color;
224 self
225 }
226
227 #[inline]
251 pub fn layouter(mut self, layouter: &'t mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>) -> Self {
252 self.layouter = Some(layouter);
253
254 self
255 }
256
257 #[inline]
261 pub fn interactive(mut self, interactive: bool) -> Self {
262 self.interactive = interactive;
263 self
264 }
265
266 #[inline]
268 pub fn frame(mut self, frame: bool) -> Self {
269 self.frame = frame;
270 self
271 }
272
273 #[inline]
275 pub fn margin(mut self, margin: impl Into<Margin>) -> Self {
276 self.margin = margin.into();
277 self
278 }
279
280 #[inline]
283 pub fn desired_width(mut self, desired_width: f32) -> Self {
284 self.desired_width = Some(desired_width);
285 self
286 }
287
288 #[inline]
292 pub fn desired_rows(mut self, desired_height_rows: usize) -> Self {
293 self.desired_height_rows = desired_height_rows;
294 self
295 }
296
297 #[inline]
303 pub fn lock_focus(mut self, tab_will_indent: bool) -> Self {
304 self.event_filter.tab = tab_will_indent;
305 self
306 }
307
308 #[inline]
312 pub fn cursor_at_end(mut self, b: bool) -> Self {
313 self.cursor_at_end = b;
314 self
315 }
316
317 #[inline]
323 pub fn clip_text(mut self, b: bool) -> Self {
324 if !self.multiline {
326 self.clip_text = b;
327 }
328 self
329 }
330
331 #[inline]
335 pub fn char_limit(mut self, limit: usize) -> Self {
336 self.char_limit = limit;
337 self
338 }
339
340 #[inline]
342 pub fn horizontal_align(mut self, align: Align) -> Self {
343 self.align.0[0] = align;
344 self
345 }
346
347 #[inline]
349 pub fn vertical_align(mut self, align: Align) -> Self {
350 self.align.0[1] = align;
351 self
352 }
353
354 #[inline]
356 pub fn min_size(mut self, min_size: Vec2) -> Self {
357 self.min_size = min_size;
358 self
359 }
360
361 #[inline]
368 pub fn return_key(mut self, return_key: impl Into<Option<KeyboardShortcut>>) -> Self {
369 self.return_key = return_key.into();
370 self
371 }
372}
373
374impl<'t> Widget for TextEdit<'t> {
377 fn ui(self, ui: &mut Ui) -> Response {
378 self.show(ui).response
379 }
380}
381
382impl<'t> TextEdit<'t> {
383 pub fn show(self, ui: &mut Ui) -> TextEditOutput {
399 let is_mutable = self.text.is_mutable();
400 let frame = self.frame;
401 let where_to_put_background = ui.painter().add(Shape::Noop);
402
403 let margin = self.margin;
404 let mut output = self.show_content(ui);
405
406 let outer_rect = output.response.rect;
409 let inner_rect = outer_rect - margin;
410 output.response.rect = inner_rect;
411
412 if frame {
413 let visuals = ui.style().interact(&output.response);
414 let frame_rect = outer_rect.expand(visuals.expansion);
415 let shape = if is_mutable {
416 if output.response.has_focus() {
417 epaint::RectShape::new(
418 frame_rect,
419 visuals.rounding,
420 ui.visuals().extreme_bg_color,
421 ui.visuals().selection.stroke,
422 )
423 } else {
424 epaint::RectShape::new(
425 frame_rect,
426 visuals.rounding,
427 ui.visuals().extreme_bg_color,
428 visuals.bg_stroke, )
430 }
431 } else {
432 let visuals = &ui.style().visuals.widgets.inactive;
433 epaint::RectShape::stroke(
434 frame_rect,
435 visuals.rounding,
436 visuals.bg_stroke, )
438 };
439
440 ui.painter().set(where_to_put_background, shape);
441 }
442
443 output
444 }
445
446 fn show_content(self, ui: &mut Ui) -> TextEditOutput {
447 let TextEdit {
448 text,
449 hint_text,
450 hint_text_font,
451 id,
452 id_source,
453 font_selection,
454 text_color,
455 layouter,
456 password,
457 frame: _,
458 margin,
459 multiline,
460 interactive,
461 desired_width,
462 desired_height_rows,
463 event_filter,
464 cursor_at_end,
465 min_size,
466 align,
467 clip_text,
468 char_limit,
469 return_key,
470 } = self;
471
472 let text_color = text_color
473 .or(ui.visuals().override_text_color)
474 .unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
476
477 let prev_text = text.as_str().to_owned();
478
479 let font_id = font_selection.resolve(ui.style());
480 let row_height = ui.fonts(|f| f.row_height(&font_id));
481 const MIN_WIDTH: f32 = 24.0; let available_width = (ui.available_width() - margin.sum().x).at_least(MIN_WIDTH);
483 let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
484 let wrap_width = if ui.layout().horizontal_justify() {
485 available_width
486 } else {
487 desired_width.min(available_width)
488 };
489
490 let font_id_clone = font_id.clone();
491 let mut default_layouter = move |ui: &Ui, text: &str, wrap_width: f32| {
492 let text = mask_if_password(password, text);
493 let layout_job = if multiline {
494 LayoutJob::simple(text, font_id_clone.clone(), text_color, wrap_width)
495 } else {
496 LayoutJob::simple_singleline(text, font_id_clone.clone(), text_color)
497 };
498 ui.fonts(|f| f.layout_job(layout_job))
499 };
500
501 let layouter = layouter.unwrap_or(&mut default_layouter);
502
503 let mut galley = layouter(ui, text.as_str(), wrap_width);
504
505 let desired_width = if clip_text {
506 wrap_width } else {
508 galley.size().x.max(wrap_width)
509 };
510 let desired_height = (desired_height_rows.at_least(1) as f32) * row_height;
511 let desired_inner_size = vec2(desired_width, galley.size().y.max(desired_height));
512 let desired_outer_size = (desired_inner_size + margin.sum()).at_least(min_size);
513 let (auto_id, outer_rect) = ui.allocate_space(desired_outer_size);
514 let rect = outer_rect - margin; let id = id.unwrap_or_else(|| {
517 if let Some(id_source) = id_source {
518 ui.make_persistent_id(id_source)
519 } else {
520 auto_id }
522 });
523 let mut state = TextEditState::load(ui.ctx(), id).unwrap_or_default();
524
525 let allow_drag_to_select =
530 ui.input(|i| !i.has_touch_screen()) || ui.memory(|mem| mem.has_focus(id));
531
532 let sense = if interactive {
533 if allow_drag_to_select {
534 Sense::click_and_drag()
535 } else {
536 Sense::click()
537 }
538 } else {
539 Sense::hover()
540 };
541 let mut response = ui.interact(outer_rect, id, sense);
542
543 response.fake_primary_click = false; let text_clip_rect = rect;
546 let painter = ui.painter_at(text_clip_rect.expand(1.0)); if interactive {
549 if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() {
550 if response.hovered() && text.is_mutable() {
551 ui.output_mut(|o| o.mutable_text_under_cursor = true);
552 }
553
554 let singleline_offset = vec2(state.singleline_offset, 0.0);
557 let cursor_at_pointer =
558 galley.cursor_from_pos(pointer_pos - rect.min + singleline_offset);
559
560 if ui.visuals().text_cursor.preview
561 && response.hovered()
562 && ui.input(|i| i.pointer.is_moving())
563 {
564 let cursor_rect =
566 cursor_rect(rect.min, &galley, &cursor_at_pointer, row_height);
567 text_selection::visuals::paint_cursor_end(&painter, ui.visuals(), cursor_rect);
568 }
569
570 let is_being_dragged = ui.ctx().is_being_dragged(response.id);
571 let did_interact = state.cursor.pointer_interaction(
572 ui,
573 &response,
574 cursor_at_pointer,
575 &galley,
576 is_being_dragged,
577 );
578
579 if did_interact {
580 ui.memory_mut(|mem| mem.request_focus(response.id));
581 }
582 }
583 }
584
585 if interactive && response.hovered() {
586 ui.ctx().set_cursor_icon(CursorIcon::Text);
587 }
588
589 let mut cursor_range = None;
590 let prev_cursor_range = state.cursor.range(&galley);
591 if interactive && ui.memory(|mem| mem.has_focus(id)) {
592 ui.memory_mut(|mem| mem.set_focus_lock_filter(id, event_filter));
593
594 let default_cursor_range = if cursor_at_end {
595 CursorRange::one(galley.end())
596 } else {
597 CursorRange::default()
598 };
599
600 let (changed, new_cursor_range) = events(
601 ui,
602 &mut state,
603 text,
604 &mut galley,
605 layouter,
606 id,
607 wrap_width,
608 multiline,
609 password,
610 default_cursor_range,
611 char_limit,
612 event_filter,
613 return_key,
614 );
615
616 if changed {
617 response.mark_changed();
618 }
619 cursor_range = Some(new_cursor_range);
620 }
621
622 let mut galley_pos = align
623 .align_size_within_rect(galley.size(), rect)
624 .intersect(rect) .min;
626 let align_offset = rect.left() - galley_pos.x;
627
628 if clip_text && align_offset == 0.0 {
630 let cursor_pos = match (cursor_range, ui.memory(|mem| mem.has_focus(id))) {
631 (Some(cursor_range), true) => galley.pos_from_cursor(&cursor_range.primary).min.x,
632 _ => 0.0,
633 };
634
635 let mut offset_x = state.singleline_offset;
636 let visible_range = offset_x..=offset_x + desired_inner_size.x;
637
638 if !visible_range.contains(&cursor_pos) {
639 if cursor_pos < *visible_range.start() {
640 offset_x = cursor_pos;
641 } else {
642 offset_x = cursor_pos - desired_inner_size.x;
643 }
644 }
645
646 offset_x = offset_x
647 .at_most(galley.size().x - desired_inner_size.x)
648 .at_least(0.0);
649
650 state.singleline_offset = offset_x;
651 galley_pos -= vec2(offset_x, 0.0);
652 } else {
653 state.singleline_offset = align_offset;
654 }
655
656 let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
657 (cursor_range, prev_cursor_range)
658 {
659 prev_cursor_range.as_ccursor_range() != cursor_range.as_ccursor_range()
660 } else {
661 false
662 };
663
664 if ui.is_rect_visible(rect) {
665 painter.galley(galley_pos, galley.clone(), text_color);
666
667 if text.as_str().is_empty() && !hint_text.is_empty() {
668 let hint_text_color = ui.visuals().weak_text_color();
669 let hint_text_font_id = hint_text_font.unwrap_or(font_id.into());
670 let galley = if multiline {
671 hint_text.into_galley(
672 ui,
673 Some(TextWrapMode::Wrap),
674 desired_inner_size.x,
675 hint_text_font_id,
676 )
677 } else {
678 hint_text.into_galley(
679 ui,
680 Some(TextWrapMode::Extend),
681 f32::INFINITY,
682 hint_text_font_id,
683 )
684 };
685 painter.galley(rect.min, galley, hint_text_color);
686 }
687
688 if ui.memory(|mem| mem.has_focus(id)) {
689 if let Some(cursor_range) = state.cursor.range(&galley) {
690 paint_text_selection(
693 &painter,
694 ui.visuals(),
695 galley_pos,
696 &galley,
697 &cursor_range,
698 None,
699 );
700
701 let primary_cursor_rect =
702 cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height);
703
704 let is_fully_visible = ui.clip_rect().contains_rect(rect); if (response.changed || selection_changed) && !is_fully_visible {
706 ui.scroll_to_rect(primary_cursor_rect, None);
708 }
709
710 if text.is_mutable() && interactive {
711 let now = ui.ctx().input(|i| i.time);
712 if response.changed || selection_changed {
713 state.last_edit_time = now;
714 }
715
716 if ui.ctx().input(|i| i.focused) {
721 text_selection::visuals::paint_text_cursor(
722 ui,
723 &painter,
724 primary_cursor_rect,
725 now - state.last_edit_time,
726 );
727 }
728
729 let transform = ui
731 .memory(|m| m.layer_transforms.get(&ui.layer_id()).copied())
732 .unwrap_or_default();
733
734 ui.ctx().output_mut(|o| {
735 o.ime = Some(crate::output::IMEOutput {
736 rect: transform * rect,
737 cursor_rect: transform * primary_cursor_rect,
738 });
739 });
740 }
741 }
742 }
743 }
744
745 state.clone().store(ui.ctx(), id);
746
747 if response.changed {
748 response.widget_info(|| {
749 WidgetInfo::text_edit(
750 ui.is_enabled(),
751 mask_if_password(password, prev_text.as_str()),
752 mask_if_password(password, text.as_str()),
753 )
754 });
755 } else if selection_changed {
756 let cursor_range = cursor_range.unwrap();
757 let char_range =
758 cursor_range.primary.ccursor.index..=cursor_range.secondary.ccursor.index;
759 let info = WidgetInfo::text_selection_changed(
760 ui.is_enabled(),
761 char_range,
762 mask_if_password(password, text.as_str()),
763 );
764 response.output_event(OutputEvent::TextSelectionChanged(info));
765 } else {
766 response.widget_info(|| {
767 WidgetInfo::text_edit(
768 ui.is_enabled(),
769 mask_if_password(password, prev_text.as_str()),
770 mask_if_password(password, text.as_str()),
771 )
772 });
773 }
774
775 #[cfg(feature = "accesskit")]
776 {
777 let role = if password {
778 accesskit::Role::PasswordInput
779 } else if multiline {
780 accesskit::Role::MultilineTextInput
781 } else {
782 accesskit::Role::TextInput
783 };
784
785 crate::text_selection::accesskit_text::update_accesskit_for_text_widget(
786 ui.ctx(),
787 id,
788 cursor_range,
789 role,
790 galley_pos,
791 &galley,
792 );
793 }
794
795 TextEditOutput {
796 response,
797 galley,
798 galley_pos,
799 text_clip_rect,
800 state,
801 cursor_range,
802 }
803 }
804}
805
806fn mask_if_password(is_password: bool, text: &str) -> String {
807 fn mask_password(text: &str) -> String {
808 std::iter::repeat(epaint::text::PASSWORD_REPLACEMENT_CHAR)
809 .take(text.chars().count())
810 .collect::<String>()
811 }
812
813 if is_password {
814 mask_password(text)
815 } else {
816 text.to_owned()
817 }
818}
819
820#[allow(clippy::too_many_arguments)]
824fn events(
825 ui: &crate::Ui,
826 state: &mut TextEditState,
827 text: &mut dyn TextBuffer,
828 galley: &mut Arc<Galley>,
829 layouter: &mut dyn FnMut(&Ui, &str, f32) -> Arc<Galley>,
830 id: Id,
831 wrap_width: f32,
832 multiline: bool,
833 password: bool,
834 default_cursor_range: CursorRange,
835 char_limit: usize,
836 event_filter: EventFilter,
837 return_key: Option<KeyboardShortcut>,
838) -> (bool, CursorRange) {
839 let os = ui.ctx().os();
840
841 let mut cursor_range = state.cursor.range(galley).unwrap_or(default_cursor_range);
842
843 state.undoer.lock().feed_state(
846 ui.input(|i| i.time),
847 &(cursor_range.as_ccursor_range(), text.as_str().to_owned()),
848 );
849
850 let copy_if_not_password = |ui: &Ui, text: String| {
851 if !password {
852 ui.ctx().copy_text(text);
853 }
854 };
855
856 let mut any_change = false;
857
858 let events = ui.input(|i| i.filtered_events(&event_filter));
859 for event in &events {
860 let did_mutate_text = match event {
861 event if cursor_range.on_event(os, event, galley, id) => None,
863
864 Event::Copy => {
865 if cursor_range.is_empty() {
866 copy_if_not_password(ui, text.as_str().to_owned());
867 } else {
868 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
869 }
870 None
871 }
872 Event::Cut => {
873 if cursor_range.is_empty() {
874 copy_if_not_password(ui, text.take());
875 Some(CCursorRange::default())
876 } else {
877 copy_if_not_password(ui, cursor_range.slice_str(text.as_str()).to_owned());
878 Some(CCursorRange::one(text.delete_selected(&cursor_range)))
879 }
880 }
881 Event::Paste(text_to_insert) => {
882 if !text_to_insert.is_empty() {
883 let mut ccursor = text.delete_selected(&cursor_range);
884
885 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
886
887 Some(CCursorRange::one(ccursor))
888 } else {
889 None
890 }
891 }
892 Event::Text(text_to_insert) => {
893 if !text_to_insert.is_empty() && text_to_insert != "\n" && text_to_insert != "\r" {
895 let mut ccursor = text.delete_selected(&cursor_range);
896
897 text.insert_text_at(&mut ccursor, text_to_insert, char_limit);
898
899 Some(CCursorRange::one(ccursor))
900 } else {
901 None
902 }
903 }
904 Event::Key {
905 key: Key::Tab,
906 pressed: true,
907 modifiers,
908 ..
909 } if multiline => {
910 let mut ccursor = text.delete_selected(&cursor_range);
911 if modifiers.shift {
912 text.decrease_indentation(&mut ccursor);
914 } else {
915 text.insert_text_at(&mut ccursor, "\t", char_limit);
916 }
917 Some(CCursorRange::one(ccursor))
918 }
919 Event::Key {
920 key,
921 pressed: true,
922 modifiers,
923 ..
924 } if return_key.is_some_and(|return_key| {
925 *key == return_key.logical_key && modifiers.matches_logically(return_key.modifiers)
926 }) =>
927 {
928 if multiline {
929 let mut ccursor = text.delete_selected(&cursor_range);
930 text.insert_text_at(&mut ccursor, "\n", char_limit);
931 Some(CCursorRange::one(ccursor))
933 } else {
934 ui.memory_mut(|mem| mem.surrender_focus(id)); break;
936 }
937 }
938 Event::Key {
939 key: Key::Z,
940 pressed: true,
941 modifiers,
942 ..
943 } if modifiers.matches_logically(Modifiers::COMMAND) => {
944 if let Some((undo_ccursor_range, undo_txt)) = state
945 .undoer
946 .lock()
947 .undo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
948 {
949 text.replace_with(undo_txt);
950 Some(*undo_ccursor_range)
951 } else {
952 None
953 }
954 }
955 Event::Key {
956 key,
957 pressed: true,
958 modifiers,
959 ..
960 } if (modifiers.matches_logically(Modifiers::COMMAND) && *key == Key::Y)
961 || (modifiers.matches_logically(Modifiers::SHIFT | Modifiers::COMMAND)
962 && *key == Key::Z) =>
963 {
964 if let Some((redo_ccursor_range, redo_txt)) = state
965 .undoer
966 .lock()
967 .redo(&(cursor_range.as_ccursor_range(), text.as_str().to_owned()))
968 {
969 text.replace_with(redo_txt);
970 Some(*redo_ccursor_range)
971 } else {
972 None
973 }
974 }
975
976 Event::Key {
977 modifiers,
978 key,
979 pressed: true,
980 ..
981 } => check_for_mutating_key_press(os, &cursor_range, text, galley, modifiers, *key),
982
983 Event::Ime(ime_event) => match ime_event {
984 ImeEvent::Enabled => {
985 state.ime_enabled = true;
986 state.ime_cursor_range = cursor_range;
987 None
988 }
989 ImeEvent::Preedit(text_mark) => {
990 if text_mark == "\n" || text_mark == "\r" {
991 None
992 } else {
993 let mut ccursor = text.delete_selected(&cursor_range);
996 let start_cursor = ccursor;
997 if !text_mark.is_empty() {
998 text.insert_text_at(&mut ccursor, text_mark, char_limit);
999 }
1000 state.ime_cursor_range = cursor_range;
1001 Some(CCursorRange::two(start_cursor, ccursor))
1002 }
1003 }
1004 ImeEvent::Commit(prediction) => {
1005 if prediction == "\n" || prediction == "\r" {
1006 None
1007 } else {
1008 state.ime_enabled = false;
1009
1010 if !prediction.is_empty()
1011 && cursor_range.secondary.ccursor.index
1012 == state.ime_cursor_range.secondary.ccursor.index
1013 {
1014 let mut ccursor = text.delete_selected(&cursor_range);
1015 text.insert_text_at(&mut ccursor, prediction, char_limit);
1016 Some(CCursorRange::one(ccursor))
1017 } else {
1018 let ccursor = cursor_range.primary.ccursor;
1019 Some(CCursorRange::one(ccursor))
1020 }
1021 }
1022 }
1023 ImeEvent::Disabled => {
1024 state.ime_enabled = false;
1025 None
1026 }
1027 },
1028
1029 _ => None,
1030 };
1031
1032 if let Some(new_ccursor_range) = did_mutate_text {
1033 any_change = true;
1034
1035 *galley = layouter(ui, text.as_str(), wrap_width);
1037
1038 cursor_range = CursorRange {
1040 primary: galley.from_ccursor(new_ccursor_range.primary),
1041 secondary: galley.from_ccursor(new_ccursor_range.secondary),
1042 };
1043 }
1044 }
1045
1046 state.cursor.set_range(Some(cursor_range));
1047
1048 state.undoer.lock().feed_state(
1049 ui.input(|i| i.time),
1050 &(cursor_range.as_ccursor_range(), text.as_str().to_owned()),
1051 );
1052
1053 (any_change, cursor_range)
1054}
1055
1056fn check_for_mutating_key_press(
1060 os: OperatingSystem,
1061 cursor_range: &CursorRange,
1062 text: &mut dyn TextBuffer,
1063 galley: &Galley,
1064 modifiers: &Modifiers,
1065 key: Key,
1066) -> Option<CCursorRange> {
1067 match key {
1068 Key::Backspace => {
1069 let ccursor = if modifiers.mac_cmd {
1070 text.delete_paragraph_before_cursor(galley, cursor_range)
1071 } else if let Some(cursor) = cursor_range.single() {
1072 if modifiers.alt || modifiers.ctrl {
1073 text.delete_previous_word(cursor.ccursor)
1075 } else {
1076 text.delete_previous_char(cursor.ccursor)
1077 }
1078 } else {
1079 text.delete_selected(cursor_range)
1080 };
1081 Some(CCursorRange::one(ccursor))
1082 }
1083
1084 Key::Delete if !modifiers.shift || os != OperatingSystem::Windows => {
1085 let ccursor = if modifiers.mac_cmd {
1086 text.delete_paragraph_after_cursor(galley, cursor_range)
1087 } else if let Some(cursor) = cursor_range.single() {
1088 if modifiers.alt || modifiers.ctrl {
1089 text.delete_next_word(cursor.ccursor)
1091 } else {
1092 text.delete_next_char(cursor.ccursor)
1093 }
1094 } else {
1095 text.delete_selected(cursor_range)
1096 };
1097 let ccursor = CCursor {
1098 prefer_next_row: true,
1099 ..ccursor
1100 };
1101 Some(CCursorRange::one(ccursor))
1102 }
1103
1104 Key::H if modifiers.ctrl => {
1105 let ccursor = text.delete_previous_char(cursor_range.primary.ccursor);
1106 Some(CCursorRange::one(ccursor))
1107 }
1108
1109 Key::K if modifiers.ctrl => {
1110 let ccursor = text.delete_paragraph_after_cursor(galley, cursor_range);
1111 Some(CCursorRange::one(ccursor))
1112 }
1113
1114 Key::U if modifiers.ctrl => {
1115 let ccursor = text.delete_paragraph_before_cursor(galley, cursor_range);
1116 Some(CCursorRange::one(ccursor))
1117 }
1118
1119 Key::W if modifiers.ctrl => {
1120 let ccursor = if let Some(cursor) = cursor_range.single() {
1121 text.delete_previous_word(cursor.ccursor)
1122 } else {
1123 text.delete_selected(cursor_range)
1124 };
1125 Some(CCursorRange::one(ccursor))
1126 }
1127
1128 _ => None,
1129 }
1130}