diff --git a/crates/egui/src/text_selection/visuals.rs b/crates/egui/src/text_selection/visuals.rs index d86f9dc5..2a31d1e1 100644 --- a/crates/egui/src/text_selection/visuals.rs +++ b/crates/egui/src/text_selection/visuals.rs @@ -96,8 +96,19 @@ pub fn paint_text_selection( pub fn paint_cursor_end(painter: &Painter, visuals: &Visuals, cursor_rect: Rect) { let stroke = visuals.text_cursor.stroke; - let top = cursor_rect.center_top(); - let bottom = cursor_rect.center_bottom(); + // Ensure the cursor is aligned to the pixel grid for whole number widths. + // See https://github.com/emilk/egui/issues/5164 + let (top, bottom) = if (stroke.width as usize) % 2 == 0 { + ( + painter.round_pos_to_pixels(cursor_rect.center_top()), + painter.round_pos_to_pixels(cursor_rect.center_bottom()), + ) + } else { + ( + painter.round_pos_to_pixel_center(cursor_rect.center_top()), + painter.round_pos_to_pixel_center(cursor_rect.center_bottom()), + ) + }; painter.line_segment([top, bottom], (stroke.width, stroke.color)); @@ -121,14 +132,14 @@ pub fn paint_text_cursor( ui: &Ui, painter: &Painter, primary_cursor_rect: Rect, - time_since_last_edit: f64, + time_since_last_interaction: f64, ) { if ui.visuals().text_cursor.blink { let on_duration = ui.visuals().text_cursor.on_duration; let off_duration = ui.visuals().text_cursor.off_duration; let total_duration = on_duration + off_duration; - let time_in_cycle = (time_since_last_edit % (total_duration as f64)) as f32; + let time_in_cycle = (time_since_last_interaction % (total_duration as f64)) as f32; let wake_in = if time_in_cycle < on_duration { // Cursor is visible diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index 7c37c6a6..012d8c8f 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -603,6 +603,8 @@ impl<'t> TextEdit<'t> { if did_interact || response.clicked() { ui.memory_mut(|mem| mem.request_focus(response.id)); + + state.last_interaction_time = ui.ctx().input(|i| i.time); } } } @@ -746,7 +748,7 @@ impl<'t> TextEdit<'t> { if text.is_mutable() && interactive { let now = ui.ctx().input(|i| i.time); if response.changed || selection_changed { - state.last_edit_time = now; + state.last_interaction_time = now; } // Only show (and blink) cursor if the egui viewport has focus. @@ -759,7 +761,7 @@ impl<'t> TextEdit<'t> { ui, &painter, primary_cursor_rect, - now - state.last_edit_time, + now - state.last_interaction_time, ); } diff --git a/crates/egui/src/widgets/text_edit/state.rs b/crates/egui/src/widgets/text_edit/state.rs index e73664a1..cbbb6071 100644 --- a/crates/egui/src/widgets/text_edit/state.rs +++ b/crates/egui/src/widgets/text_edit/state.rs @@ -53,10 +53,10 @@ pub struct TextEditState { #[cfg_attr(feature = "serde", serde(skip))] pub(crate) singleline_offset: f32, - /// When did the user last press a key? + /// When did the user last press a key or click on the `TextEdit`. /// Used to pause the cursor animation when typing. #[cfg_attr(feature = "serde", serde(skip))] - pub(crate) last_edit_time: f64, + pub(crate) last_interaction_time: f64, } impl TextEditState {