Smooth out zooming with discreet scroll wheel (#4530)

* closes https://github.com/emilk/egui/issues/4525

You can zoom in using ctrl/cmd + scrolling. When using a discreet scroll
wheel, the zoom factor now gets smoothed.
This commit is contained in:
Emil Ernerfeldt 2024-05-23 09:56:36 +02:00 committed by GitHub
parent 8db8f6df82
commit 8433b43231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 66 additions and 30 deletions

View File

@ -42,6 +42,11 @@ pub struct InputState {
/// Used for smoothing the scroll delta.
unprocessed_scroll_delta: Vec2,
/// Used for smoothing the scroll delta when zooming.
unprocessed_scroll_delta_for_zoom: f32,
/// You probably want to use [`Self::smooth_scroll_delta`] instead.
///
/// The raw input of how many points the user scrolled.
///
/// The delta dictates how the _content_ should move.
@ -152,6 +157,7 @@ impl Default for InputState {
pointer: Default::default(),
touch_states: Default::default(),
unprocessed_scroll_delta: Vec2::ZERO,
unprocessed_scroll_delta_for_zoom: 0.0,
raw_scroll_delta: Vec2::ZERO,
smooth_scroll_delta: Vec2::ZERO,
zoom_factor_delta: 1.0,
@ -201,8 +207,11 @@ impl InputState {
let mut keys_down = self.keys_down;
let mut zoom_factor_delta = 1.0; // TODO(emilk): smoothing for zoom factor
let mut raw_scroll_delta = Vec2::ZERO;
let mut unprocessed_scroll_delta = self.unprocessed_scroll_delta;
let mut unprocessed_scroll_delta_for_zoom = self.unprocessed_scroll_delta_for_zoom;
let mut smooth_scroll_delta = Vec2::ZERO;
let mut smooth_scroll_delta_for_zoom = 0.0;
for event in &mut new.events {
match event {
@ -240,29 +249,34 @@ impl InputState {
MouseWheelUnit::Page => screen_rect.height() * *delta,
};
if modifiers.ctrl || modifiers.command {
// Treat as zoom instead:
let factor = (delta.y / 200.0).exp();
zoom_factor_delta *= factor;
} else {
if modifiers.shift {
// Treat as horizontal scrolling.
// Note: one Mac we already get horizontal scroll events when shift is down.
delta = vec2(delta.x + delta.y, 0.0);
if modifiers.shift {
// Treat as horizontal scrolling.
// Note: one Mac we already get horizontal scroll events when shift is down.
delta = vec2(delta.x + delta.y, 0.0);
}
raw_scroll_delta += delta;
// Mouse wheels often go very large steps.
// A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta.
// So we smooth it out over several frames for a nicer user experience when scrolling in egui.
// BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing,
// because it adds latency.
let is_smooth = match unit {
MouseWheelUnit::Point => delta.length() < 8.0, // a bit arbitrary here
MouseWheelUnit::Line | MouseWheelUnit::Page => false,
};
let is_zoom = modifiers.ctrl || modifiers.mac_cmd || modifiers.command;
#[allow(clippy::collapsible_else_if)]
if is_zoom {
if is_smooth {
smooth_scroll_delta_for_zoom += delta.y;
} else {
unprocessed_scroll_delta_for_zoom += delta.y;
}
raw_scroll_delta += delta;
// Mouse wheels often go very large steps.
// A single notch on a logitech mouse wheel connected to a Macbook returns 14.0 raw_scroll_delta.
// So we smooth it out over several frames for a nicer user experience when scrolling in egui.
// BUT: if the user is using a nice smooth mac trackpad, we don't add smoothing,
// because it adds latency.
let is_smooth = match unit {
MouseWheelUnit::Point => delta.length() < 5.0, // a bit arbitrary here
MouseWheelUnit::Line | MouseWheelUnit::Page => false,
};
} else {
if is_smooth {
smooth_scroll_delta += delta;
} else {
@ -281,22 +295,39 @@ impl InputState {
let dt = stable_dt.at_most(0.1);
let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt); // reach _% in _ seconds. TODO(emilk): parameterize
for d in 0..2 {
if unprocessed_scroll_delta[d].abs() < 1.0 {
smooth_scroll_delta[d] += unprocessed_scroll_delta[d];
unprocessed_scroll_delta[d] = 0.0;
} else {
let applied = t * unprocessed_scroll_delta[d];
smooth_scroll_delta[d] += applied;
unprocessed_scroll_delta[d] -= applied;
if unprocessed_scroll_delta != Vec2::ZERO {
for d in 0..2 {
if unprocessed_scroll_delta[d].abs() < 1.0 {
smooth_scroll_delta[d] += unprocessed_scroll_delta[d];
unprocessed_scroll_delta[d] = 0.0;
} else {
let applied = t * unprocessed_scroll_delta[d];
smooth_scroll_delta[d] += applied;
unprocessed_scroll_delta[d] -= applied;
}
}
}
{
// Smooth scroll-to-zoom:
if unprocessed_scroll_delta_for_zoom.abs() < 1.0 {
smooth_scroll_delta_for_zoom += unprocessed_scroll_delta_for_zoom;
unprocessed_scroll_delta_for_zoom = 0.0;
} else {
let applied = t * unprocessed_scroll_delta_for_zoom;
smooth_scroll_delta_for_zoom += applied;
unprocessed_scroll_delta_for_zoom -= applied;
}
zoom_factor_delta *= (smooth_scroll_delta_for_zoom / 200.0).exp();
}
}
Self {
pointer,
touch_states: self.touch_states,
unprocessed_scroll_delta,
unprocessed_scroll_delta_for_zoom,
raw_scroll_delta,
smooth_scroll_delta,
zoom_factor_delta,
@ -369,6 +400,7 @@ impl InputState {
pub fn wants_repaint(&self) -> bool {
self.pointer.wants_repaint()
|| self.unprocessed_scroll_delta.abs().max_elem() > 0.2
|| self.unprocessed_scroll_delta_for_zoom.abs() > 0.2
|| !self.events.is_empty()
// We need to wake up and check for press-and-hold for the context menu.
@ -1157,6 +1189,7 @@ impl InputState {
touch_states,
unprocessed_scroll_delta,
unprocessed_scroll_delta_for_zoom,
raw_scroll_delta,
smooth_scroll_delta,
@ -1198,6 +1231,9 @@ impl InputState {
ui.label(format!(
"unprocessed_scroll_delta: {unprocessed_scroll_delta:?} points"
));
ui.label(format!(
"unprocessed_scroll_delta_for_zoom: {unprocessed_scroll_delta_for_zoom:?} points"
));
}
ui.label(format!("raw_scroll_delta: {raw_scroll_delta:?} points"));
ui.label(format!(