Skip to content

Commit

Permalink
Make text cursor always appear on click (#5420)
Browse files Browse the repository at this point in the history
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* [x] I have followed the instructions in the PR template

### Problem
When clicking on a TextEdit sometimes the cursor doesn't appear
immediately which makes it feel like the click was not registered for a
second. This is because the start time for the blinking animation is
only reset on keyboard input, but not on mouse interaction.

It's hard to tell on the video but the cursor doesn't show immediately
after clicking if the blink timer happens to be off.


https://github.com/user-attachments/assets/9f049bd0-0375-4291-b2ef-697777fb854d


### Solution
Reset the click timer every time a `TextEdit` is clicked. 

Additionally, the cursor is now correctly painted on the pixel boundary.
IMO we should default to 1px cursor (instead of 2px) but that's not
included in this PR. Happy to make that change too.


https://github.com/user-attachments/assets/6c489414-f2c4-4dc6-85dd-f8bc457edad0
  • Loading branch information
juancampa authored Dec 4, 2024
1 parent eac7ba0 commit cd0f585
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 8 deletions.
19 changes: 15 additions & 4 deletions crates/egui/src/text_selection/visuals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions crates/egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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.
Expand All @@ -759,7 +761,7 @@ impl<'t> TextEdit<'t> {
ui,
&painter,
primary_cursor_rect,
now - state.last_edit_time,
now - state.last_interaction_time,
);
}

Expand Down
4 changes: 2 additions & 2 deletions crates/egui/src/widgets/text_edit/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit cd0f585

Please sign in to comment.