feat: kinetic scrolling on wayland (#54)
* wayland: add kinetic scroll controller * wayland: wire kinetic scrolling into pointer events * wayland: cancel kinetic scrolling on pointer and gesture interruption * wayland: add kinetic scroll tests
This commit is contained in:
@@ -2,6 +2,7 @@ mod client;
|
||||
mod clipboard;
|
||||
mod cursor;
|
||||
mod display;
|
||||
mod scroll;
|
||||
mod serial;
|
||||
mod window;
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ use wayland_client::{
|
||||
},
|
||||
};
|
||||
use wayland_protocols::wp::pointer_gestures::zv1::client::{
|
||||
zwp_pointer_gesture_pinch_v1, zwp_pointer_gestures_v1,
|
||||
zwp_pointer_gesture_hold_v1, zwp_pointer_gesture_pinch_v1, zwp_pointer_gestures_v1,
|
||||
};
|
||||
use wayland_protocols::wp::primary_selection::zv1::client::zwp_primary_selection_offer_v1::{
|
||||
self, ZwpPrimarySelectionOfferV1,
|
||||
@@ -74,6 +74,7 @@ use xkbcommon::xkb::{self, KEYMAP_COMPILE_NO_FLAGS, Keycode};
|
||||
|
||||
use super::{
|
||||
display::WaylandDisplay,
|
||||
scroll::KineticScrollController,
|
||||
window::{ImeInput, WaylandWindowStatePtr},
|
||||
};
|
||||
|
||||
@@ -221,6 +222,7 @@ pub(crate) struct WaylandClientState {
|
||||
wl_seat: wl_seat::WlSeat, // TODO: Multi seat support
|
||||
wl_pointer: Option<wl_pointer::WlPointer>,
|
||||
pinch_gesture: Option<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1>,
|
||||
hold_gesture: Option<zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1>,
|
||||
pinch_scale: f32,
|
||||
wl_keyboard: Option<wl_keyboard::WlKeyboard>,
|
||||
cursor_shape_device: Option<wp_cursor_shape_device_v1::WpCursorShapeDeviceV1>,
|
||||
@@ -248,6 +250,7 @@ pub(crate) struct WaylandClientState {
|
||||
pub mouse_location: Option<Point<Pixels>>,
|
||||
continuous_scroll_delta: Option<Point<Pixels>>,
|
||||
discrete_scroll_delta: Option<Point<f32>>,
|
||||
kinetic_scroll: KineticScrollController,
|
||||
vertical_modifier: f32,
|
||||
horizontal_modifier: f32,
|
||||
scroll_event_received: bool,
|
||||
@@ -673,6 +676,7 @@ impl WaylandClient {
|
||||
wl_pointer: None,
|
||||
wl_keyboard: None,
|
||||
pinch_gesture: None,
|
||||
hold_gesture: None,
|
||||
pinch_scale: 1.0,
|
||||
cursor_shape_device: None,
|
||||
data_device,
|
||||
@@ -719,6 +723,7 @@ impl WaylandClient {
|
||||
mouse_location: None,
|
||||
continuous_scroll_delta: None,
|
||||
discrete_scroll_delta: None,
|
||||
kinetic_scroll: KineticScrollController::new(),
|
||||
vertical_modifier: -1.0,
|
||||
horizontal_modifier: -1.0,
|
||||
button_pressed: None,
|
||||
@@ -1186,10 +1191,18 @@ impl Dispatch<WlCallback, ObjectId> for WaylandClientStatePtr {
|
||||
let Some(window) = get_window(&mut state, surface_id) else {
|
||||
return;
|
||||
};
|
||||
let kinetic_input = if let wl_callback::Event::Done { .. } = event {
|
||||
state.kinetic_scroll.tick(&window)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
drop(state);
|
||||
|
||||
if let wl_callback::Event::Done { .. } = event {
|
||||
window.frame();
|
||||
if let Some((window, input)) = kinetic_input {
|
||||
window.handle_input(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1440,6 +1453,13 @@ impl Dispatch<wl_seat::WlSeat, ()> for WaylandClientStatePtr {
|
||||
},
|
||||
);
|
||||
|
||||
state.hold_gesture = state.globals.gesture_manager.as_ref().and_then(
|
||||
|gesture_manager: &zwp_pointer_gestures_v1::ZwpPointerGesturesV1| {
|
||||
(gesture_manager.version() >= 3)
|
||||
.then(|| gesture_manager.get_hold_gesture(&pointer, qh, ()))
|
||||
},
|
||||
);
|
||||
|
||||
if let Some(wl_pointer) = &state.wl_pointer {
|
||||
wl_pointer.release();
|
||||
}
|
||||
@@ -1856,6 +1876,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
}
|
||||
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
|
||||
state.restore_cursor_after_hide();
|
||||
let kinetic_input = state.kinetic_scroll.cancel();
|
||||
|
||||
if let Some(window) = state.mouse_focused_window.clone() {
|
||||
if window.is_blocked() {
|
||||
@@ -1895,6 +1916,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
modifiers: state.modifiers,
|
||||
});
|
||||
drop(state);
|
||||
if let Some((window, input)) = kinetic_input {
|
||||
window.handle_input(input);
|
||||
}
|
||||
window.handle_input(input);
|
||||
}
|
||||
}
|
||||
@@ -1996,6 +2020,9 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
if state.axis_source == AxisSource::Wheel {
|
||||
return;
|
||||
}
|
||||
if state.axis_source == AxisSource::Finger {
|
||||
state.kinetic_scroll.start_finger_scroll();
|
||||
}
|
||||
let axis = if state.modifiers.shift {
|
||||
wl_pointer::Axis::HorizontalScroll
|
||||
} else {
|
||||
@@ -2076,21 +2103,48 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::AxisStop { .. } => {
|
||||
if state.axis_source == AxisSource::Finger
|
||||
&& state.kinetic_scroll.stop_finger_scroll()
|
||||
{
|
||||
state.scroll_event_received = true;
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::Frame => {
|
||||
if state.scroll_event_received {
|
||||
state.scroll_event_received = false;
|
||||
let continuous = state.continuous_scroll_delta.take();
|
||||
let discrete = state.discrete_scroll_delta.take();
|
||||
if let Some(continuous) = continuous {
|
||||
let touch_phase = state.kinetic_scroll.touch_phase();
|
||||
if state.axis_source == AxisSource::Finger {
|
||||
state
|
||||
.kinetic_scroll
|
||||
.record_delta(Instant::now(), continuous);
|
||||
}
|
||||
let mut kinetic_input = None;
|
||||
if state.kinetic_scroll.has_pending_stop() {
|
||||
if let (Some(window), Some(position)) =
|
||||
(state.mouse_focused_window.clone(), state.mouse_location)
|
||||
{
|
||||
let modifiers = state.modifiers;
|
||||
kinetic_input = state
|
||||
.kinetic_scroll
|
||||
.finish_pending_stop(window, position, modifiers);
|
||||
}
|
||||
}
|
||||
if let Some(window) = state.mouse_focused_window.clone() {
|
||||
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position: state.mouse_location.unwrap(),
|
||||
delta: ScrollDelta::Pixels(continuous),
|
||||
modifiers: state.modifiers,
|
||||
touch_phase: TouchPhase::Moved,
|
||||
touch_phase,
|
||||
});
|
||||
drop(state);
|
||||
window.handle_input(input);
|
||||
if let Some((window, input)) = kinetic_input {
|
||||
window.handle_input(input);
|
||||
}
|
||||
}
|
||||
} else if let Some(discrete) = discrete
|
||||
&& let Some(window) = state.mouse_focused_window.clone()
|
||||
@@ -2103,6 +2157,18 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
||||
});
|
||||
drop(state);
|
||||
window.handle_input(input);
|
||||
} else if state.kinetic_scroll.has_pending_stop()
|
||||
&& let (Some(window), Some(position)) =
|
||||
(state.mouse_focused_window.clone(), state.mouse_location)
|
||||
{
|
||||
let modifiers = state.modifiers;
|
||||
if let Some((window, input)) = state
|
||||
.kinetic_scroll
|
||||
.finish_pending_stop(window, position, modifiers)
|
||||
{
|
||||
drop(state);
|
||||
window.handle_input(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2151,6 +2217,11 @@ impl Dispatch<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1, ()>
|
||||
surface: _,
|
||||
fingers: _,
|
||||
} => {
|
||||
if let Some((window, input)) = state.kinetic_scroll.cancel() {
|
||||
drop(state);
|
||||
window.handle_input(input);
|
||||
state = client.borrow_mut();
|
||||
}
|
||||
state.pinch_scale = 1.0;
|
||||
let input = PlatformInput::Pinch(PinchEvent {
|
||||
position: state.mouse_location.unwrap_or(point(px(0.0), px(0.0))),
|
||||
@@ -2196,6 +2267,26 @@ impl Dispatch<zwp_pointer_gesture_pinch_v1::ZwpPointerGesturePinchV1, ()>
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1, ()> for WaylandClientStatePtr {
|
||||
fn event(
|
||||
this: &mut Self,
|
||||
_: &zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1,
|
||||
event: <zwp_pointer_gesture_hold_v1::ZwpPointerGestureHoldV1 as Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let zwp_pointer_gesture_hold_v1::Event::Begin { .. } = event {
|
||||
let client = this.get_client();
|
||||
let mut state = client.borrow_mut();
|
||||
if let Some((window, input)) = state.kinetic_scroll.cancel() {
|
||||
drop(state);
|
||||
window.handle_input(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wp_fractional_scale_v1::WpFractionalScaleV1, ObjectId> for WaylandClientStatePtr {
|
||||
fn event(
|
||||
this: &mut Self,
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use super::window::WaylandWindowStatePtr;
|
||||
use gpui::{
|
||||
Modifiers, Pixels, PlatformInput, Point, ScrollDelta, ScrollWheelEvent, TouchPhase, point, px,
|
||||
};
|
||||
|
||||
const KINETIC_SCROLL_HISTORY_WINDOW: Duration = Duration::from_millis(150);
|
||||
const KINETIC_SCROLL_FRICTION: f32 = 4.0;
|
||||
const KINETIC_SCROLL_STOP_VELOCITY: f32 = 5.0;
|
||||
const KINETIC_SCROLL_MAX_VELOCITY: f32 = 6000.0;
|
||||
|
||||
pub(crate) struct KineticScrollController {
|
||||
history: KineticScrollHistory,
|
||||
id: u64,
|
||||
scroll: Option<KineticScroll>,
|
||||
finger_active: bool,
|
||||
finger_start_pending: bool,
|
||||
finger_stop_pending: bool,
|
||||
}
|
||||
|
||||
struct KineticScrollHistory {
|
||||
entries: VecDeque<(Instant, Point<Pixels>)>,
|
||||
displacement: Point<Pixels>,
|
||||
}
|
||||
|
||||
struct KineticScroll {
|
||||
id: u64,
|
||||
window: WaylandWindowStatePtr,
|
||||
position: Point<Pixels>,
|
||||
modifiers: Modifiers,
|
||||
velocity: Point<Pixels>,
|
||||
last_time: Instant,
|
||||
}
|
||||
|
||||
impl KineticScrollController {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
history: KineticScrollHistory::new(),
|
||||
id: 0,
|
||||
scroll: None,
|
||||
finger_active: false,
|
||||
finger_start_pending: false,
|
||||
finger_stop_pending: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_finger_scroll(&mut self) {
|
||||
self.id += 1;
|
||||
self.scroll = None;
|
||||
self.finger_stop_pending = false;
|
||||
if !self.finger_active {
|
||||
self.finger_active = true;
|
||||
self.finger_start_pending = true;
|
||||
self.history.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stop_finger_scroll(&mut self) -> bool {
|
||||
if self.finger_active {
|
||||
self.finger_stop_pending = true;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn touch_phase(&mut self) -> TouchPhase {
|
||||
if self.finger_start_pending {
|
||||
self.finger_start_pending = false;
|
||||
TouchPhase::Started
|
||||
} else {
|
||||
TouchPhase::Moved
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn record_delta(&mut self, time: Instant, delta: Point<Pixels>) {
|
||||
self.history.push(time, delta);
|
||||
}
|
||||
|
||||
pub(crate) fn has_pending_stop(&self) -> bool {
|
||||
self.finger_stop_pending
|
||||
}
|
||||
|
||||
pub(crate) fn finish_pending_stop(
|
||||
&mut self,
|
||||
window: WaylandWindowStatePtr,
|
||||
position: Point<Pixels>,
|
||||
modifiers: Modifiers,
|
||||
) -> Option<(WaylandWindowStatePtr, PlatformInput)> {
|
||||
self.finger_stop_pending = false;
|
||||
self.finger_active = false;
|
||||
self.finger_start_pending = false;
|
||||
let velocity = self.history.velocity(Instant::now());
|
||||
self.history.clear();
|
||||
self.start(window, position, modifiers, velocity)
|
||||
}
|
||||
|
||||
pub(crate) fn tick(
|
||||
&mut self,
|
||||
window: &WaylandWindowStatePtr,
|
||||
) -> Option<(WaylandWindowStatePtr, PlatformInput)> {
|
||||
let kinetic_scroll = self.scroll.as_mut()?;
|
||||
if kinetic_scroll.id != self.id || !kinetic_scroll.window.ptr_eq(window) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let elapsed = now
|
||||
.duration_since(kinetic_scroll.last_time)
|
||||
.as_secs_f32()
|
||||
.min(0.05);
|
||||
kinetic_scroll.last_time = now;
|
||||
|
||||
let delta = point(
|
||||
px(f32::from(kinetic_scroll.velocity.x) * elapsed),
|
||||
px(f32::from(kinetic_scroll.velocity.y) * elapsed),
|
||||
);
|
||||
let velocity_multiplier = (-KINETIC_SCROLL_FRICTION * elapsed).exp();
|
||||
kinetic_scroll.velocity.x = px(f32::from(kinetic_scroll.velocity.x) * velocity_multiplier);
|
||||
kinetic_scroll.velocity.y = px(f32::from(kinetic_scroll.velocity.y) * velocity_multiplier);
|
||||
|
||||
let finished = is_kinetic_scroll_stopped(kinetic_scroll.velocity);
|
||||
|
||||
let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position: kinetic_scroll.position,
|
||||
delta: ScrollDelta::Pixels(delta),
|
||||
modifiers: kinetic_scroll.modifiers,
|
||||
touch_phase: if finished {
|
||||
TouchPhase::Ended
|
||||
} else {
|
||||
TouchPhase::Moved
|
||||
},
|
||||
});
|
||||
|
||||
let window = kinetic_scroll.window.clone();
|
||||
if finished {
|
||||
self.finger_active = false;
|
||||
self.finger_start_pending = false;
|
||||
self.finger_stop_pending = false;
|
||||
self.scroll = None;
|
||||
self.history.clear();
|
||||
}
|
||||
|
||||
Some((window, input))
|
||||
}
|
||||
|
||||
pub(crate) fn cancel(&mut self) -> Option<(WaylandWindowStatePtr, PlatformInput)> {
|
||||
let kinetic_scroll = self.scroll.take()?;
|
||||
|
||||
self.id += 1;
|
||||
self.finger_active = false;
|
||||
self.finger_start_pending = false;
|
||||
self.finger_stop_pending = false;
|
||||
self.history.clear();
|
||||
|
||||
Some((
|
||||
kinetic_scroll.window,
|
||||
PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position: kinetic_scroll.position,
|
||||
delta: ScrollDelta::Pixels(point(px(0.0), px(0.0))),
|
||||
modifiers: kinetic_scroll.modifiers,
|
||||
touch_phase: TouchPhase::Ended,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
fn start(
|
||||
&mut self,
|
||||
window: WaylandWindowStatePtr,
|
||||
position: Point<Pixels>,
|
||||
modifiers: Modifiers,
|
||||
velocity: Point<Pixels>,
|
||||
) -> Option<(WaylandWindowStatePtr, PlatformInput)> {
|
||||
self.id += 1;
|
||||
if is_kinetic_scroll_stopped(velocity) {
|
||||
return Some((
|
||||
window,
|
||||
PlatformInput::ScrollWheel(ScrollWheelEvent {
|
||||
position,
|
||||
delta: ScrollDelta::Pixels(point(px(0.0), px(0.0))),
|
||||
modifiers,
|
||||
touch_phase: TouchPhase::Ended,
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
let id = self.id;
|
||||
self.scroll = Some(KineticScroll {
|
||||
id,
|
||||
window,
|
||||
position,
|
||||
modifiers,
|
||||
velocity,
|
||||
last_time: Instant::now(),
|
||||
});
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl KineticScrollHistory {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
entries: VecDeque::new(),
|
||||
displacement: point(px(0.0), px(0.0)),
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, time: Instant, delta: Point<Pixels>) {
|
||||
self.entries.push_back((time, delta));
|
||||
self.displacement += delta;
|
||||
let cutoff = time - KINETIC_SCROLL_HISTORY_WINDOW;
|
||||
while self
|
||||
.entries
|
||||
.front()
|
||||
.is_some_and(|(entry_time, _)| *entry_time < cutoff)
|
||||
{
|
||||
let delta = self
|
||||
.entries
|
||||
.pop_front()
|
||||
.map(|(_, delta)| delta)
|
||||
.unwrap_or(point(px(0.0), px(0.0)));
|
||||
self.displacement -= delta;
|
||||
}
|
||||
}
|
||||
|
||||
fn velocity(&self, now: Instant) -> Point<Pixels> {
|
||||
let Some((first_time, _)) = self.entries.front() else {
|
||||
return point(px(0.0), px(0.0));
|
||||
};
|
||||
|
||||
let duration = now.duration_since(*first_time).as_secs_f32();
|
||||
if duration == 0.0 {
|
||||
return point(px(0.0), px(0.0));
|
||||
}
|
||||
|
||||
let delta = self.displacement;
|
||||
|
||||
point(
|
||||
px((f32::from(delta.x) / duration)
|
||||
.clamp(-KINETIC_SCROLL_MAX_VELOCITY, KINETIC_SCROLL_MAX_VELOCITY)),
|
||||
px((f32::from(delta.y) / duration)
|
||||
.clamp(-KINETIC_SCROLL_MAX_VELOCITY, KINETIC_SCROLL_MAX_VELOCITY)),
|
||||
)
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.entries.clear();
|
||||
self.displacement = point(px(0.0), px(0.0));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_kinetic_scroll_stopped(velocity: Point<Pixels>) -> bool {
|
||||
f32::from(velocity.x).abs() < KINETIC_SCROLL_STOP_VELOCITY
|
||||
&& f32::from(velocity.y).abs() < KINETIC_SCROLL_STOP_VELOCITY
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_kinetic_scroll_stopped() {
|
||||
assert!(is_kinetic_scroll_stopped(point(px(0.0), px(0.0))));
|
||||
assert!(is_kinetic_scroll_stopped(point(px(4.9), px(4.9))));
|
||||
assert!(is_kinetic_scroll_stopped(point(px(-4.9), px(4.9))));
|
||||
assert!(!is_kinetic_scroll_stopped(point(px(5.0), px(0.0))));
|
||||
assert!(!is_kinetic_scroll_stopped(point(px(0.0), px(5.0))));
|
||||
assert!(!is_kinetic_scroll_stopped(point(px(100.0), px(100.0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_velocity_empty() {
|
||||
let history = KineticScrollHistory::new();
|
||||
let velocity = history.velocity(Instant::now());
|
||||
assert_eq!(f32::from(velocity.x), 0.0);
|
||||
assert_eq!(f32::from(velocity.y), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_velocity_single_entry() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(
|
||||
now - Duration::from_millis(100),
|
||||
point(px(100.0), px(200.0)),
|
||||
);
|
||||
let velocity = history.velocity(now);
|
||||
assert!((f32::from(velocity.x) - 1000.0).abs() < 1.0);
|
||||
assert!((f32::from(velocity.y) - 2000.0).abs() < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_velocity_multiple_entries() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(now - Duration::from_millis(100), point(px(50.0), px(0.0)));
|
||||
history.push(now - Duration::from_millis(50), point(px(50.0), px(100.0)));
|
||||
let velocity = history.velocity(now);
|
||||
assert!((f32::from(velocity.x) - 1000.0).abs() < 1.0);
|
||||
assert!((f32::from(velocity.y) - 1000.0).abs() < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_velocity_zero_duration() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(now, point(px(100.0), px(200.0)));
|
||||
let velocity = history.velocity(now);
|
||||
assert_eq!(f32::from(velocity.x), 0.0);
|
||||
assert_eq!(f32::from(velocity.y), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_velocity_clamped() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(
|
||||
now - Duration::from_millis(1),
|
||||
point(px(60000.0), px(-60000.0)),
|
||||
);
|
||||
let velocity = history.velocity(now);
|
||||
assert_eq!(f32::from(velocity.x), KINETIC_SCROLL_MAX_VELOCITY);
|
||||
assert_eq!(f32::from(velocity.y), -KINETIC_SCROLL_MAX_VELOCITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_prunes_old_entries() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(
|
||||
now - Duration::from_millis(300),
|
||||
point(px(9999.0), px(9999.0)),
|
||||
);
|
||||
history.push(
|
||||
now - Duration::from_millis(100),
|
||||
point(px(100.0), px(100.0)),
|
||||
);
|
||||
let velocity = history.velocity(now);
|
||||
assert!((f32::from(velocity.x) - 1000.0).abs() < 1.0);
|
||||
assert!((f32::from(velocity.y) - 1000.0).abs() < 1.0);
|
||||
assert_eq!(history.entries.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_history_clear() {
|
||||
let mut history = KineticScrollHistory::new();
|
||||
let now = Instant::now();
|
||||
history.push(now, point(px(100.0), px(100.0)));
|
||||
assert_eq!(history.entries.len(), 1);
|
||||
history.clear();
|
||||
assert_eq!(history.entries.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_touch_phase_sequence() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
assert!(matches!(scroller.touch_phase(), TouchPhase::Moved));
|
||||
|
||||
scroller.start_finger_scroll();
|
||||
assert!(matches!(scroller.touch_phase(), TouchPhase::Started));
|
||||
assert!(matches!(scroller.touch_phase(), TouchPhase::Moved));
|
||||
assert!(matches!(scroller.touch_phase(), TouchPhase::Moved));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_finger_scroll_id_increments() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
let id_before = scroller.id;
|
||||
scroller.start_finger_scroll();
|
||||
assert_eq!(scroller.id, id_before + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_finger_scroll_resets_stop_pending() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
scroller.start_finger_scroll();
|
||||
assert!(scroller.stop_finger_scroll());
|
||||
assert!(scroller.has_pending_stop());
|
||||
scroller.start_finger_scroll();
|
||||
assert!(!scroller.has_pending_stop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_start_finger_scroll_does_not_reset_active() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
scroller.start_finger_scroll();
|
||||
assert!(scroller.finger_active);
|
||||
assert!(scroller.finger_start_pending);
|
||||
|
||||
scroller.touch_phase();
|
||||
assert!(!scroller.finger_start_pending);
|
||||
|
||||
scroller.start_finger_scroll();
|
||||
assert!(scroller.finger_active);
|
||||
assert!(!scroller.finger_start_pending);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stop_finger_scroll_when_inactive() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
assert!(!scroller.stop_finger_scroll());
|
||||
assert!(!scroller.has_pending_stop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stop_finger_scroll_when_active() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
scroller.start_finger_scroll();
|
||||
assert!(scroller.stop_finger_scroll());
|
||||
assert!(scroller.has_pending_stop());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_record_delta_and_has_pending_stop() {
|
||||
let mut scroller = KineticScrollController::new();
|
||||
scroller.start_finger_scroll();
|
||||
scroller.record_delta(Instant::now(), point(px(10.0), px(20.0)));
|
||||
assert_eq!(scroller.history.entries.len(), 1);
|
||||
assert!(!scroller.has_pending_stop());
|
||||
|
||||
scroller.stop_finger_scroll();
|
||||
assert!(scroller.has_pending_stop());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user