diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 22a5623f077..ad923a7c9f5 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -76,6 +76,7 @@ pub struct TextEdit<'t> { desired_height_rows: usize, event_filter: EventFilter, cursor_at_end: bool, + blink: bool, min_size: Vec2, align: Align2, clip_text: bool, @@ -132,6 +133,7 @@ impl<'t> TextEdit<'t> { ..Default::default() }, cursor_at_end: true, + blink: false, min_size: Vec2::ZERO, align: Align2::LEFT_TOP, clip_text: false, @@ -305,6 +307,15 @@ impl<'t> TextEdit<'t> { self } + /// When `false` (default), the text cursor will not blink. + /// + /// When `true`, the text cursor will blink. + #[inline] + pub fn blink(mut self, b: bool) -> Self { + self.blink = b; + self + } + /// When `true` (default), overflowing text will be clipped. /// /// When `false`, widget width will expand to make all text visible. @@ -413,7 +424,7 @@ impl<'t> TextEdit<'t> { frame_rect, visuals.rounding, ui.visuals().extreme_bg_color, - visuals.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop". + ui.visuals().widgets.unhovered.bg_stroke, // TODO(emilk): we want to show something here, or a text-edit field doesn't "pop". ) } } else { @@ -449,6 +460,7 @@ impl<'t> TextEdit<'t> { desired_height_rows, event_filter, cursor_at_end, + blink, min_size, align, clip_text, @@ -508,6 +520,7 @@ impl<'t> TextEdit<'t> { } }); let mut state = TextEditState::load(ui.ctx(), id).unwrap_or_default(); + let save_ccursor_range = state.cursor.char_range(); // On touch screens (e.g. mobile in `eframe` web), should // dragging select text, or scroll the enclosing [`ScrollArea`] (if any)? @@ -528,6 +541,7 @@ impl<'t> TextEdit<'t> { let mut response = ui.interact(rect, id, sense); let text_clip_rect = rect; let painter = ui.painter_at(text_clip_rect.expand(1.0)); // expand to avoid clipping cursor + let i_time = ui.input(|i| i.time); if interactive { if let Some(pointer_pos) = ui.ctx().pointer_interact_pos() { @@ -548,7 +562,10 @@ impl<'t> TextEdit<'t> { // preview: let cursor_rect = cursor_rect(response.rect.min, &galley, &cursor_at_pointer, row_height); - paint_cursor(&painter, ui.visuals(), cursor_rect); + let is_drawn = paint_cursor(&painter, ui.visuals(), cursor_rect, i_time, false); + if is_drawn { + ui.ctx().request_repaint(); + } } let is_being_dragged = ui.ctx().is_being_dragged(response.id); @@ -680,7 +697,17 @@ impl<'t> TextEdit<'t> { } if text.is_mutable() { - paint_cursor(&painter, ui.visuals(), primary_cursor_rect); + let is_blink = blink && (save_ccursor_range == state.cursor.char_range()); + let is_drawn = paint_cursor( + &painter, + ui.visuals(), + primary_cursor_rect, + i_time, + is_blink, + ); + if is_drawn { + ui.ctx().request_repaint(); + } if interactive { // For IME, so only set it when text is editable and visible! @@ -940,6 +967,7 @@ fn events( if !text_mark.is_empty() { text.insert_text_at(&mut ccursor, text_mark, char_limit); } + state.ime_cursor_range = cursor_range; Some(CCursorRange::two(start_cursor, ccursor)) } else { None @@ -947,12 +975,16 @@ fn events( } Event::CompositionEnd(prediction) => { - // CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first, so do not check `state.has_ime = true` in the following statement. + // CompositionEnd only characters may be typed into TextEdit without trigger CompositionStart first, + // so do not check `state.has_ime = true` in the following statement. if prediction != "\n" && prediction != "\r" { state.has_ime = false; - let mut ccursor = text.delete_selected(&cursor_range); - if !prediction.is_empty() { + let mut ccursor; + if !prediction.is_empty() && cursor_range == state.ime_cursor_range { + ccursor = text.delete_selected(&cursor_range); text.insert_text_at(&mut ccursor, prediction, char_limit); + } else { + ccursor = cursor_range.primary.ccursor; } Some(CCursorRange::one(ccursor)) } else {