/* 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 MINIMUM_TICK_TIME = 8;
var _now = Date.now;
/**
* Wrapper for DOM mouse events. Converts
*
* `mousedown` -> `start`
* `mousemove` -> `update`
* `mouseup` -> `end`
*
* MouseInput emits these events with the following payload data:
*
* `value` - Displacement in pixels from `mousedown`
* `delta` - Differential in pixels between successive mouse positions
* `velocity` - Velocity of mouse movement in pixels per second
* `cumulate` - Accumulated value over successive displacements
* `clientX` - DOM event clientX property
* `clientY` - DOM event clientY property
* `offsetX` - DOM event offsetX property
* `offsetY` - DOM event offsetY property
*
* @example
*
* var surface = new Surface({
* size : [100,100],
* properties : {background : 'red'}
* });
*
* var mouseInput = new MouseInput({
* direction : MouseInput.DIRECTION.X
* });
*
* mouseInput.subscribe(surface);
*
* mouseInput.on('start', function(payload){
* // fired on mouse down
* console.log('start', payload);
* });
*
* mouseInput.on('update', function(payload){
* // fired on mouse move
* console.log('update', payload);
* });
*
* mouseInput.on('end', function(payload){
* // fired on mouse up
* console.log('end', payload);
* });
*
* @class MouseInput
* @constructor
* @extend SimpleStream
* @param [options] {Object} Options
* @param [options.direction] {Number} Direction to project movement onto.
* Options found in MouseInput.DIRECTION.
* @param [options.scale=1] {Number} Scale the response to the mouse
*/
function MouseInput(options) {
this.options = OptionsManager.setOptions(this, options);
this._eventInput = new EventHandler();
this._eventOutput = new EventHandler();
EventHandler.setInputHandler(this, this._eventInput);
EventHandler.setOutputHandler(this, this._eventOutput);
this._eventInput.on('mousedown', handleStart.bind(this));
this._eventInput.on('mousemove', handleMove.bind(this));
this._eventInput.on('mouseup', handleEnd.bind(this));
this._eventInput.on('mouseleave', handleLeave.bind(this));
this._payload = {
delta : null,
value : null,
cumulate : null,
velocity : null,
clientX : 0,
clientY : 0,
offsetX : 0,
offsetY : 0
};
this._value = null;
this._cumulate = null;
this._prevCoord = undefined;
this._prevTime = undefined;
this._down = false;
this._move = false;
}
MouseInput.prototype = Object.create(SimpleStream.prototype);
MouseInput.prototype.constructor = MouseInput;
MouseInput.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
*/
MouseInput.DIRECTION = {
X : 0,
Y : 1
};
function handleStart(event) {
var delta;
var velocity;
event.preventDefault(); // prevent drag
var x = event.clientX;
var y = event.clientY;
this._prevCoord = [x, y];
this._prevTime = _now();
this._down = true;
this._move = false;
if (this.options.direction !== undefined) {
if (this._cumulate === null) this._cumulate = 0;
this._value = 0;
delta = 0;
velocity = 0;
}
else {
if (this._cumulate === null) this._cumulate = [0, 0];
this._value = [0, 0];
delta = [0, 0];
velocity = [0, 0];
}
var payload = this._payload;
payload.delta = delta;
payload.value = this._value;
payload.cumulate = this._cumulate;
payload.velocity = velocity;
payload.clientX = x;
payload.clientY = y;
payload.offsetX = event.offsetX;
payload.offsetY = event.offsetY;
this._eventOutput.emit('start', payload);
}
function handleMove(event){
if (!this._down) return false;
var scale = this.options.scale;
var prevCoord = this._prevCoord;
var prevTime = this._prevTime;
var x = event.clientX;
var y = event.clientY;
var currTime = _now();
var diffX = scale * (x - prevCoord[0]);
var diffY = scale * (y - prevCoord[1]);
var dt = Math.max(currTime - prevTime, MINIMUM_TICK_TIME); // minimum tick time
var inv_dt = 1 / dt;
var velX = diffX * inv_dt;
var velY = diffY * inv_dt;
var nextVel;
var nextDelta;
if (this.options.direction === MouseInput.DIRECTION.X) {
nextDelta = diffX;
nextVel = velX;
this._value += nextDelta;
this._cumulate += nextDelta;
}
else if (this.options.direction === MouseInput.DIRECTION.Y) {
nextDelta = diffY;
nextVel = velY;
this._value += nextDelta;
this._cumulate += nextDelta;
}
else {
nextDelta = [diffX, diffY];
nextVel = [velX, 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.value = this._value;
payload.cumulate = this._cumulate;
payload.velocity = nextVel;
payload.clientX = x;
payload.clientY = y;
payload.offsetX = event.offsetX;
payload.offsetY = event.offsetY;
this._eventOutput.emit('update', payload);
this._prevCoord = [x, y];
this._prevTime = currTime;
this._move = true;
}
function handleEnd() {
if (!this._down) return false;
this._eventOutput.emit('end', this._payload);
this._prevCoord = undefined;
this._prevTime = undefined;
this._down = false;
this._move = false;
}
function handleLeave(event) {
if (!this._down || !this._move) return;
var boundMove = handleMove.bind(this);
var boundEnd = function(event) {
handleEnd.call(this, event);
document.removeEventListener('mousemove', boundMove);
document.removeEventListener('mouseup', boundEnd);
}.bind(this, event);
document.addEventListener('mousemove', boundMove);
document.addEventListener('mouseup', boundEnd);
}
module.exports = MouseInput;
});