Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smooth out zooming with discreet scroll wheel #4530

Merged
merged 1 commit into from
May 23, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 63 additions & 27 deletions crates/egui/src/input_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
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,
};
// 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;
}
} else {
if is_smooth {
smooth_scroll_delta += delta;
} else {
Expand All @@ -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;
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[d];
smooth_scroll_delta[d] += applied;
unprocessed_scroll_delta[d] -= applied;
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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1157,6 +1189,7 @@ impl InputState {
touch_states,

unprocessed_scroll_delta,
unprocessed_scroll_delta_for_zoom,
raw_scroll_delta,
smooth_scroll_delta,

Expand Down Expand Up @@ -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!(
Expand Down
Loading