/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @license MPL 2.0
* @copyright Famous Industries, Inc. 2014
*/
/* Modified work copyright © 2015-2016 David Valdman */
define(function(require, exports, module) {
var EventHandler = require('../events/EventHandler');
var OptionsManager = require('../core/OptionsManager');
var SimpleStream = require('../streams/SimpleStream');
var Timer = require('../core/Timer');
var MINIMUM_TICK_TIME = 8;
var MAX_DIFFERENTIAL = 50; // caps mousewheel differentials
/**
* Wrapper for DOM wheel/mousewheel events. Converts `scroll` events
* to `start`, `update` and `end` events and emits them with the payload:
*
* `value` - Scroll displacement in pixels from start
* `delta` - Scroll differential in pixels between subsequent events
* `velocity` - Velocity of scroll,
* `clientX` - DOM event clientX property
* `clientY` - DOM event clientY property
* `offsetX` - DOM event offsetX property
* `offsetY` - DOM event offsetY property
*
* @example
*
* var scrollInput = new ScrollInput();
*
* scrollInput.subscribe(Engine) // listens on `window` events
*
* scrollInput.on('start', function(payload){
* console.log('start', payload);
* });
*
* scrollInput.on('update', function(payload){
* console.log('update', payload);
* });
*
* scrollInput.on('end', function(payload){
* console.log('end', payload);
* });
*
* @class ScrollInput
* @constructor
* @extends Streams.SimpleStream
* @uses Inputs.TouchTracker
* @uses Core.OptionsManager
* @param [options] {Object} Options
* @param [options.direction] {Number} Direction to project movement onto.
* Options found in TouchInput.DIRECTION.
* @param [options.scale=1] {Number} Scale the response to the mouse
*/
function ScrollInput(options) {
this.options = OptionsManager.setOptions(this, options);
this._payload = {
delta : null,
value : null,
cumulate : null,
velocity : null,
clientX : undefined,
clientY : undefined,
offsetX : undefined,
offsetY : undefined
};
this._eventInput = new EventHandler();
this._eventOutput = new EventHandler();
EventHandler.setInputHandler(this, this._eventInput);
EventHandler.setOutputHandler(this, this._eventOutput);
this._eventInput.on('wheel', handleMove.bind(this));
this._value = (this.options.direction === undefined) ? [0,0] : 0;
this._cumulate = (this.options.direction === undefined) ? [0, 0] : 0;
this._prevTime = undefined;
this._inProgress = false;
var self = this;
this._scrollEnd = Timer.debounce(function(){
self._inProgress = false;
// this prevents velocities for mousewheel events vs trackpad ones
if (self._payload.delta !== 0){
self._payload.velocity = 0;
}
self._eventOutput.emit('end', self._payload);
}, 100);
}
ScrollInput.prototype = Object.create(SimpleStream.prototype);
ScrollInput.prototype.constructor = ScrollInput;
ScrollInput.DEFAULT_OPTIONS = {
direction: undefined,
scale: 1
};
/**
* Constrain the input along a specific axis.
*
* @property DIRECTION {Object}
* @property DIRECTION.X {Number} x-axis
* @property DIRECTION.Y {Number} y-axis
* @static
*/
ScrollInput.DIRECTION = {
X : 0,
Y : 1
};
var _now = Date.now;
function handleMove(event) {
event.preventDefault(); // Disable default scrolling behavior
if (!this._inProgress) {
this._value = (this.options.direction === undefined) ? [0,0] : 0;
payload = this._payload;
payload.value = this._value;
payload.cumulate = this._cumulate;
payload.clientX = event.clientX;
payload.clientY = event.clientY;
payload.offsetX = event.offsetX;
payload.offsetY = event.offsetY;
this._eventOutput.emit('start', payload);
this._inProgress = true;
return;
}
var currTime = _now();
var prevTime = this._prevTime || currTime;
var diffX = -event.deltaX;
var diffY = -event.deltaY;
if (diffX > MAX_DIFFERENTIAL) diffX = MAX_DIFFERENTIAL;
if (diffY > MAX_DIFFERENTIAL) diffY = MAX_DIFFERENTIAL;
if (diffX < -MAX_DIFFERENTIAL) diffX = -MAX_DIFFERENTIAL;
if (diffY < -MAX_DIFFERENTIAL) diffY = -MAX_DIFFERENTIAL;
var invDeltaT = 1 / Math.max(currTime - prevTime, MINIMUM_TICK_TIME); // minimum tick time
this._prevTime = currTime;
var velX = diffX * invDeltaT;
var velY = diffY * invDeltaT;
var scale = this.options.scale;
var nextVel;
var nextDelta;
if (this.options.direction === ScrollInput.DIRECTION.X) {
nextDelta = scale * diffX;
nextVel = scale * velX;
this._value += nextDelta;
this._cumulate += nextDelta;
}
else if (this.options.direction === ScrollInput.DIRECTION.Y) {
nextDelta = scale * diffY;
nextVel = scale * velY;
this._value += nextDelta;
this._cumulate += nextDelta;
}
else {
nextDelta = [scale * diffX, scale * diffY];
nextVel = [scale * velX, scale * velY];
this._value[0] += nextDelta[0];
this._value[1] += nextDelta[1];
this._cumulate[0] += nextDelta[0];
this._cumulate[1] += nextDelta[1];
}
var payload = this._payload;
payload.delta = nextDelta;
payload.velocity = nextVel;
payload.value = this._value;
payload.cumulate = this._cumulate;
this._eventOutput.emit('update', payload);
// debounce `end` event
this._scrollEnd();
}
module.exports = ScrollInput;
});