Scrub speed: a pointer gone from the window keeps its speed

egui reports the pointer gone (no hover position) with the button still down when it leaves the window during a scrub; the speed stayed at the last rise rather than falling back to 1x and announcing it. Test in field_tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor Brown
2026-11-02 05:56:06 -05:00
co-authored by Claude Fable 5.1
parent 3d704ce4c0
commit 04d37b56b5
2 changed files with 29 additions and 4 deletions
+20
View File
@@ -317,6 +317,26 @@ fn moving_up_or_down_while_scrubbing_changes_the_speed_and_says_so() {
u.frame_with(vec![Event::PointerMoved(to), press(false, to)], shift);
u.frame(vec![]);
assert_eq!(geom(&u, a).0 - x0, 200.0, "ten points at 2x with Shift");
// The pointer reported gone from the window (egui's PointerGone) with the button still down
// keeps the speed it had: no notice of 1x, and the scrub goes on at 2x once it is back.
let x0 = geom(&u, a).0;
let from = label(&mut u, "X");
let m = Modifiers::NONE;
let press = |pressed, pos| Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: m };
u.frame_with(vec![Event::PointerMoved(from), press(true, from)], m);
let up = pos2(from.x, from.y - 150.0);
u.frame_with(vec![Event::PointerMoved(up)], m);
u.frame_with(vec![Event::PointerMoved(pos2(up.x + 10.0, up.y))], m);
assert!(toast(&u).contains("2x"), "{}", toast(&u));
u.app.ui.toast = None;
u.frame_with(vec![Event::PointerGone], m);
u.frame_with(vec![Event::PointerMoved(pos2(up.x + 20.0, up.y))], m);
u.frame_with(vec![Event::PointerMoved(pos2(up.x + 30.0, up.y))], m);
assert!(toast(&u).is_empty(), "no notice while the pointer was gone or back at the same height: {}", toast(&u));
let to = pos2(up.x + 30.0, up.y);
u.frame_with(vec![Event::PointerMoved(to), press(false, to)], m);
u.frame(vec![]);
assert_eq!(geom(&u, a).0 - x0, 40.0, "ten points before and ten after the pointer was gone, at 2x both");
}
#[test]
+9 -4
View File
@@ -168,7 +168,8 @@ pub(crate) fn scrub(ui: &Ui, id: Id, response: &Response, value: Option<f64>, st
let scrub_id = id.with("scrub");
if response.dragged() || response.drag_stopped() {
let began = response.drag_started();
let (v0, mut amount, last_speed): (f64, f64, &'static str) = ui.data(|d| d.get_temp(scrub_id)).filter(|_| !began).unwrap_or((v, 0.0, ""));
let (v0, mut amount, last_speed, last_rise): (f64, f64, &'static str, f64) =
ui.data(|d| d.get_temp(scrub_id)).filter(|_| !began).unwrap_or((v, 0.0, "", 0.0));
let (dx, rise, shift, alt) = ui.input(|i| {
let p = &i.pointer;
// As it begins: all the way from the press (a drag is one only after a few points).
@@ -178,10 +179,14 @@ pub(crate) fn scrub(ui: &Ui, id: Id, response: &Response, value: Option<f64>, st
// does too. Elsewhere the scrub just stops there.
let raw = p.motion().map_or(0.0, |m| m.x / i.pixels_per_point);
let at_edge = moved == 0.0 && p.hover_pos().is_none_or(|at| at.x <= 1.0 || at.x >= i.content_rect().max.x - 1.0);
// How far above where the scrub began the pointer is: the speed (`scrub_speed`).
let rise = p.hover_pos().zip(p.press_origin()).map_or(0.0, |(at, from)| f64::from(from.y - at.y));
// How far above where the scrub began the pointer is: the speed (`scrub_speed`);
// None while the pointer is gone from the window.
let rise = p.hover_pos().zip(p.press_origin()).map(|(at, from)| f64::from(from.y - at.y));
(f64::from(if at_edge { raw } else { moved }), rise, i.modifiers.shift, i.modifiers.alt)
});
// Gone from the window (egui says so, the button still down), the pointer keeps the speed
// it had rather than falling back to the usual one.
let rise = rise.unwrap_or(last_rise);
let (speed, notice) = scrub_speed(rise);
// The frame the button goes up moves nothing: the scrub ends where the last one left it.
if response.dragged() {
@@ -190,7 +195,7 @@ pub(crate) fn scrub(ui: &Ui, id: Id, response: &Response, value: Option<f64>, st
if notice != last_speed && !(last_speed.is_empty() && speed == 1.0) {
ui.data_mut(|d| d.insert_temp(scrub_notice_id(), notice));
}
ui.data_mut(|d| d.insert_temp(scrub_id, (v0, amount, notice)));
ui.data_mut(|d| d.insert_temp(scrub_id, (v0, amount, notice, rise)));
}
let quantum = if alt { step * 0.1 } else { step };
return Some(FieldEdit::Scrub(scrubbed(v0, amount, quantum), response.drag_stopped()));