use emath::{Rect, Vec2, vec2};
use crate::{InputOptions, Modifiers, MouseWheelUnit, TouchPhase};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Status {
Static,
Smoothing,
InTouch,
}
#[derive(Clone, Debug)]
pub struct WheelState {
pub status: Status,
pub modifiers: Modifiers,
pub last_wheel_event: f64,
pub unprocessed_wheel_delta: Vec2,
pub smooth_wheel_delta: Vec2,
}
impl Default for WheelState {
fn default() -> Self {
Self {
status: Status::Static,
modifiers: Default::default(),
last_wheel_event: f64::NEG_INFINITY,
unprocessed_wheel_delta: Vec2::ZERO,
smooth_wheel_delta: Vec2::ZERO,
}
}
}
impl WheelState {
#[expect(clippy::too_many_arguments)]
pub fn on_wheel_event(
&mut self,
viewport_rect: Rect,
options: &InputOptions,
time: f64,
unit: MouseWheelUnit,
delta: Vec2,
phase: TouchPhase,
latest_modifiers: Modifiers,
) {
self.last_wheel_event = time;
match phase {
crate::TouchPhase::Start => {
self.status = Status::InTouch;
self.modifiers = latest_modifiers;
}
crate::TouchPhase::Move => {
match self.status {
Status::Static | Status::Smoothing => {
self.modifiers = latest_modifiers;
self.status = Status::Smoothing;
}
Status::InTouch => {
self.modifiers |= latest_modifiers;
}
}
let mut delta = match unit {
MouseWheelUnit::Point => delta,
MouseWheelUnit::Line => options.line_scroll_speed * delta,
MouseWheelUnit::Page => viewport_rect.height() * delta,
};
let is_horizontal = self
.modifiers
.matches_any(options.horizontal_scroll_modifier);
let is_vertical = self.modifiers.matches_any(options.vertical_scroll_modifier);
if is_horizontal && !is_vertical {
delta = vec2(delta.x + delta.y, 0.0);
}
if !is_horizontal && is_vertical {
delta = vec2(0.0, delta.x + delta.y);
}
let is_smooth = self.status == Status::InTouch
|| match unit {
MouseWheelUnit::Point => delta.length() < 8.0, MouseWheelUnit::Line | MouseWheelUnit::Page => false,
};
if is_smooth {
self.smooth_wheel_delta += delta;
} else {
self.unprocessed_wheel_delta += delta;
}
}
crate::TouchPhase::End | crate::TouchPhase::Cancel => {
self.status = Status::Static;
self.modifiers = Default::default();
self.unprocessed_wheel_delta = Default::default();
self.smooth_wheel_delta = Default::default();
}
}
}
pub fn after_events(&mut self, time: f64, dt: f32) {
let t = crate::emath::exponential_smooth_factor(0.90, 0.1, dt);
if self.unprocessed_wheel_delta != Vec2::ZERO {
for d in 0..2 {
if self.unprocessed_wheel_delta[d].abs() < 1.0 {
self.smooth_wheel_delta[d] += self.unprocessed_wheel_delta[d];
self.unprocessed_wheel_delta[d] = 0.0;
} else {
let applied = t * self.unprocessed_wheel_delta[d];
self.smooth_wheel_delta[d] += applied;
self.unprocessed_wheel_delta[d] -= applied;
}
}
}
let time_since_last_scroll = time - self.last_wheel_event;
if self.status == Status::Smoothing
&& self.smooth_wheel_delta == Vec2::ZERO
&& 0.150 < time_since_last_scroll
{
self.status = Status::Static;
self.modifiers = Default::default();
}
}
pub fn is_scrolling(&self) -> bool {
self.status != Status::Static
}
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
status,
modifiers,
last_wheel_event,
unprocessed_wheel_delta,
smooth_wheel_delta,
} = self;
let time = ui.input(|i| i.time);
crate::Grid::new("ScrollState")
.num_columns(2)
.show(ui, |ui| {
ui.label("status");
ui.monospace(format!("{status:?}"));
ui.end_row();
ui.label("modifiers");
ui.monospace(format!("{modifiers:?}"));
ui.end_row();
ui.label("last_wheel_event");
ui.monospace(format!("{:.1}s ago", time - *last_wheel_event));
ui.end_row();
ui.label("unprocessed_wheel_delta");
ui.monospace(unprocessed_wheel_delta.to_string());
ui.end_row();
ui.label("smooth_wheel_delta");
ui.monospace(smooth_wheel_delta.to_string());
ui.end_row();
});
}
}