rxjs-gestures
Version:
A library for reacting to future user input.
127 lines (107 loc) • 4.67 kB
JavaScript
import { Subscriber } from 'rxjs/Subscriber';
export class DecelerateOperator {
constructor(u, speedLimit, scheduler) {
if (u === 0) { u = 100; }
if (u < 0) { u = Math.abs(u); }
if (u > 100) {
u /= Math.pow(10, Math.ceil(Math.log10(u))) * 100;
}
this.u = u / 100;
this.scheduler = scheduler;
this.speedLimit = Math.abs(speedLimit);
}
call(subscriber, source) {
return source.subscribe(new DecelerateSubscriber(
subscriber, this.u, this.speedLimit, this.scheduler
));
}
}
export class DecelerateSubscriber extends Subscriber {
constructor(destination, u, speedLimit, scheduler) {
super(destination, null, scheduler);
this.u = u;
this.point = null;
this.hasPoint = false;
this.scheduler = scheduler;
this.speedLimit = speedLimit;
}
_next(point) {
super._next((this.hasPoint = true) && (this.point = point));
}
_complete() {
if (!this.hasPoint) {
return super._complete();
}
const { u, point, speedLimit, scheduler } = this;
const { index = 0, movementT = 1, direction } = point;
if (!index || index <= 0) {
return super._complete();
}
const time = scheduler.now();
if (time - point.time >= 25) {
super._next(point.clone({ button: 0, buttons: 0, speed: 0 }));
return super._complete();
}
const speed = Math.min(speedLimit, point.speed) || 0;
const duration = 1000 * (speed / (9.8 /*normalForce*/ * u)) || 0;
if (!duration || duration <= 0) {
return super._complete();
}
const distanceX = Math.cos(direction) * speed * duration;
const distanceY = Math.sin(direction) * speed * duration;
this.unsubscribe();
this.closed = this.isStopped = false;
this.add(scheduler.schedule(DecelerateSubscriber.dispatch, 0, {
start: point,
time, point, duration,
subscriber: this.destination,
distanceX, distanceY, scheduler,
}));
}
static dispatch(state) {
let { time, start, point,
scheduler, subscriber,
distanceX, distanceY, duration } = state;
const easingFunc = quartOut;
const now = scheduler.now();
const elapsed = Math.min(now - time, duration);
const pageX = easingFunc(elapsed, start.pageX, distanceX, duration);
const pageY = easingFunc(elapsed, start.pageY, distanceY, duration);
point = point.clone();
point.button = 0;
point.buttons = 0;
point.movementT = now - point.time;
point.movementX = pageX - point.pageX;
point.movementY = pageY - point.pageY;
point.movementXTotal = point.movementX + point.movementXTotal;
point.movementYTotal = point.movementY + point.movementYTotal;
point.x = easingFunc(elapsed, start.x, distanceX, duration);
point.y = easingFunc(elapsed, start.y, distanceY, duration);
point.speed = easingFunc(elapsed, start.speed, - start.speed, duration);
point.clientX = easingFunc(elapsed, start.clientX, distanceX, duration);
point.clientY = easingFunc(elapsed, start.clientY, distanceY, duration);
point.screenX = easingFunc(elapsed, start.screenX, distanceX, duration);
point.screenY = easingFunc(elapsed, start.screenY, distanceY, duration);
point.targetX = easingFunc(elapsed, start.targetX, distanceX, duration);
point.targetY = easingFunc(elapsed, start.targetY, distanceY, duration);
point.targetPageX = easingFunc(elapsed, start.targetPageX, distanceX, duration);
point.targetPageY = easingFunc(elapsed, start.targetPageY, distanceY, duration);
point.targetClientX = easingFunc(elapsed, start.targetClientX, distanceX, duration);
point.targetClientY = easingFunc(elapsed, start.targetClientY, distanceY, duration);
point.targetScreenX = easingFunc(elapsed, start.targetScreenX, distanceX, duration);
point.targetScreenY = easingFunc(elapsed, start.targetScreenY, distanceY, duration);
point.time = now;
point.pageX = pageX;
point.pageY = pageY;
state.point = point;
subscriber.next(point);
if (elapsed >= duration) {
subscriber.complete();
} else {
this.schedule(state, 0);
}
}
}
function quartOut(time, begin, change, duration) {
return -change * ((time = time / duration - 1) * time * time * time - 1) + begin;
}