extern crate num;
use std::f64;
mod circular_backqueue;
use std::ops;
const MAX_EVENTS_CONSIDERED: u32 = 5;
const ENABLE_VELOCITY_SMOOTHING: bool = true;
const FLING_FRICTION_FACTOR: f64 = 0.998;
const PAN_ACCELERATION_FACTOR: f64 = 1.34;
const SAMPLE_OVER_X_FRAMES: usize = 10;
type Millis = f64;
#[derive(Default)]
pub struct Scrollview {
content_height: u64,
content_width: u64,
viewport_height: u64,
viewport_width: u64,
current_velocity: AxisVector<f64>,
current_position: AxisVector<f64>,
frametime: Millis,
time_to_pageflip: Millis,
current_timestamp: u64,
interpolation_ratio: f64,
input_per_frame_log: circular_backqueue::ForgetfulLogQueue<u32>,
pan_log_x: circular_backqueue::ForgetfulLogQueue<(u64, f64)>,
pan_log_y: circular_backqueue::ForgetfulLogQueue<(u64, f64)>,
}
#[derive(Copy)]
#[derive(Clone)]
#[derive(Default)]
pub struct AxisVector<T> where T: num::Num, T: PartialOrd, T: Copy {
pub x: T,
pub y: T,
x_threshold: T,
y_threshold: T,
decaying: bool,
}
impl<T> AxisVector<T> where T: num::Num, T: PartialOrd, T: Copy {
fn difference(self, other: AxisVector<T>) -> AxisVector<T> {
AxisVector {
x: self.x - other.x,
y: self.y - other.y,
..self
}
}
fn replace(&mut self, axis: Axis, magnitude: T) {
match axis {
Axis::Horizontal => self.x = magnitude,
Axis::Vertical => self.y = magnitude,
}
}
fn get_at(&self, axis: Axis) -> T {
match axis {
Axis::Horizontal => self.x,
Axis::Vertical => self.y
}
}
fn update(&mut self, axis: Axis, magnitude: T) {
match axis {
Axis::Horizontal => self.x = magnitude + self.x,
Axis::Vertical => self.y = magnitude + self.y,
}
}
}
impl AxisVector<f64> {
fn decay_active(&self) -> bool {
self.decaying && self.x > self.x_threshold && self.y > self.y_threshold
}
fn decay_start(&mut self) {
self.decaying = true;
}
fn step_frame(&mut self) {
if self.decay_active() {
self.x = Scrollview::fling_decay(self.x);
self.y = Scrollview::fling_decay(self.y);
}
if self.x < self.x_threshold && self.y < self.y_threshold {
self.decaying = false;
}
}
}
impl<T> ops::Add<AxisVector<T>> for AxisVector<T> where T: num::Num, T: PartialOrd, T: Copy {
type Output = AxisVector<T>;
fn add(self, rhs: AxisVector<T>) -> AxisVector<T> {
AxisVector {
x: self.x + rhs.x,
y: self.y + rhs.y,
..self
}
}
}
#[derive(Copy)]
#[derive(Clone)]
pub enum Axis {
Horizontal,
Vertical,
}
pub enum Event {
Pan { timestamp: u64, axis: Axis, amount: i32 }, Fling { timestamp: u64 },
Interrupt { timestamp: u64 },
}
impl Scrollview {
pub fn new() -> Scrollview {
Scrollview {
input_per_frame_log: circular_backqueue::ForgetfulLogQueue::new(SAMPLE_OVER_X_FRAMES),
..Default::default()
}
}
pub fn del(_: Scrollview) {}
pub fn set_geometry(
&mut self,
content_height: u64,
content_width: u64,
viewport_height: u64,
viewport_width: u64,
) {
self.content_height = content_height;
self.content_width = content_width;
self.viewport_height = viewport_height;
self.viewport_width = viewport_width;
}
pub fn push_event(
&mut self,
event: &Event
) {
match event {
Event::Pan { timestamp, axis, amount } => self.push_pan(*timestamp, *axis, *amount),
Event::Fling {..} => self.push_fling(),
Event::Interrupt {..} => self.push_interrupt(),
}
}
pub fn animating(&self) -> bool {
self.current_velocity.decay_active()
}
pub fn step_frame(&mut self, timestamp: Option<u64>) {
self.interpolation_ratio = self.input_per_frame_log.all().iter().sum::<u32>() as f64 / self.input_per_frame_log.size() as f64;
self.current_timestamp = timestamp.unwrap_or(1);
self.current_velocity.step_frame();
self.update_velocity();
self.current_position.x += Self::accelerate(self.current_velocity.x) * self.interpolation_ratio * self.frametime;
self.current_position.y += Self::accelerate(self.current_velocity.y) * self.interpolation_ratio * self.frametime;
self.input_per_frame_log.push(0); }
pub fn set_avg_frametime(&mut self, milliseconds: f64) {
self.frametime = milliseconds;
}
pub fn set_next_frame_predict(&mut self, milliseconds: f64) {
self.time_to_pageflip = milliseconds;
}
pub fn get_position_absolute(&self) -> AxisVector<f64> {
self.current_position + self.get_overshoot()
}
}
impl Scrollview {
fn push_pan(&mut self, timestamp: u64, axis: Axis, amount: i32) {
match axis {
Axis::Horizontal => self.pan_log_x.push((timestamp, f64::from(amount))),
Axis::Vertical => self.pan_log_y.push((timestamp, f64::from(amount))),
}
}
fn push_fling(&mut self) {
self.current_velocity.decay_start();
}
fn push_interrupt(&mut self) {
self.pan_log_x.clear();
self.pan_log_y.clear();
self.current_velocity = AxisVector { x: 0.0, y: 0.0, ..self.current_velocity };
}
fn get_overshoot(&self) -> AxisVector<f64> {
let time_to_target = (self.frametime / 2.0) + self.time_to_pageflip;
AxisVector {
x: self.current_velocity.x * time_to_target,
y: self.current_velocity.y * time_to_target,
decaying: false,
..Default::default()
}
}
fn update_velocity(&mut self) {
if ENABLE_VELOCITY_SMOOTHING == false {
self.current_velocity = AxisVector {
x: self.pan_log_x.get_or_avg(0).1,
y: self.pan_log_y.get_or_avg(0).1,
..self.current_velocity
}
} else {
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut weight_x = 0.0;
let mut weight_y = 0.0;
let axes = vec![(&self.pan_log_x, &mut sum_x, &mut weight_x), (&self.pan_log_y, &mut sum_y, &mut weight_y)];
for (log, sum, weight) in axes {
for i in 0..(MAX_EVENTS_CONSIDERED - 1) {
match log.get(i as usize) {
None => (),
Some((timestamp, magnitude)) => {
let staleness = self.current_timestamp - timestamp;
let staleness_mult_factor = 1.0 / (staleness as f64);
*weight += staleness_mult_factor;
*sum += magnitude * staleness_mult_factor;
}
}
}
}
let avg_x = sum_x / weight_x;
let avg_y = sum_y / weight_y;
self.current_velocity = AxisVector {
x: avg_x,
y: avg_y,
..self.current_velocity
}
}
}
fn accelerate(from: f64) -> f64 {
from.powf(PAN_ACCELERATION_FACTOR)
}
fn fling_decay(from: f64) -> f64 {
from.powf(FLING_FRICTION_FACTOR)
}
}