define(["lib/jquery", "lib/underscore", "common/util/binding_list", "common/util/check"], function( $, _, BindingList, check) {
"use strict";
// Movements less than this number of pixels are ignored
var TRANSLATION_THRESHOLD = 0; //10;
// Rotations less than this number of radians are ignored
var ROTATION_THRESHOLD = 0; //0.1;
// Scaling less than this fraction is ignored
var SCALING_THRESHOLD = 0; //0.1;
// returns true iff abs(a-b) > threshold
var exceedsThreshold = function(a, b, threshold) {
return Math.abs(a - b) > threshold;
};
/**
* Makes an element draggable.
*
* @param {string | DOMElement | jQuery} el
* Draggable element. Can be a CSS selector, a DOM element, or a
* jQuery object.
*
* @param {boolean} [multitouch]
* If true, allows resizing and rotation with two fingers. Defaults to
* true.
*
* @class
* Draggables are elements that can be moved and (optionally) rotated and
* scaled using touch and pinch-to-zoom.
*
* @property {Boolean} enabled Whether this Draggable is enabled.
*
* @name Lens.Components.Draggable
*/
var Draggable = function(el, multitouch) {
check(el, check.Match.Elementish);
check(multitouch, check.Match.Optional(Boolean));
/** @alias Lens.Components.Draggable.prototype */
var self = {};
var enabled = true;
var onUpdateBindings = BindingList();
Object.defineProperty(self, "enabled", { get: function() { return enabled; } });
if(multitouch === undefined) {
multitouch = true;
}
el = $(el);
/**
* Enables the draggable. Draggables start out enabled, so you only
* need to call this if you've disabled the Draggable with `disable`.
*/
self.enable = function() {
enabled = true;
};
/**
* Disables the draggable. The draggable will no longer move when
* touched.
*/
self.disable = function() {
enabled = false;
};
/**
* Registers a callback to be run when the component is dragged.
* `onUpdateCallback` is called once per touch move event with the new
* position.
*
* @param {Function} [onUpdateCallback]
* Callback called with onUpdateCallback(x, y, el).
*/
self.updated = function(onUpdateCallback) {
check(onUpdateCallback, Function);
return onUpdateBindings.add(onUpdateCallback);
};
// current state of mover
var offset = el.offset();
var x = offset.left;
var y = offset.top;
var r = 0;
var scale = 1;
// Binding object for the move handler
var binding = null;
// updates mover to match current x, y, r, w, h
var update = function() {
var transform = "translate(" + x + "px, " + y + "px) ";
transform += "scale(" + scale + "," + scale + ") ";
transform += "rotate(" + r + "rad) ";
el.css("-webkit-transform", transform);
onUpdateBindings.callAll(x, y, el[0]);
};
update();
/**
* Set the x, y position of the draggable.
*
* @param {Number} [newX]
* New x position.
*
* @param {Number} [newY]
* New y position.
*/
self.setPosition = function(newX, newY) {
check(newX, Number);
check(newY, Number);
x = newX;
y = newY;
update();
};
// make mover fixed positioned and reset position. use transition for
// smooth movement
el.css("position", "fixed")
.css("top", 0)
.css("bottom", null)
.css("left", 0)
.css("right", null)
.css("-webkit-transition", "all 0.1s linear");
el[0].addEventListener("lens:touchstart", function(evt) {
if(!self.enabled) {
return;
}
var touches = evt.targetTouches;
// keep track of x,y,r,w,h at start of gesture
var origX = x;
var origY = y;
var origR = r;
var origScale = scale;
// keep track of previous dx, dy, rotation, and scale so we can
// ignore jitter
var prevDx = 0;
var prevDy = 0;
var prevR = 0;
var prevScale = 1;
// set the origin so rotation works correctly
el.css("-webkit-transform-origin", touches.elX + " " + touches.elY);
// clear old binding
if(binding) {
binding.clear();
}
// bind to the move handler
binding = touches.moved(function() {
if(exceedsThreshold(prevDx, touches.dx, TRANSLATION_THRESHOLD)) {
x = origX + touches.dx;
prevDx = touches.dx;
}
if(exceedsThreshold(prevDy, touches.dy, TRANSLATION_THRESHOLD)) {
y = origY + touches.dy;
prevDy = touches.dy;
}
if(multitouch) {
if(touches.rotation &&
exceedsThreshold(prevR, touches.rotation, ROTATION_THRESHOLD)) {
r = origR + touches.rotation;
prevR = touches.rotation;
}
if(exceedsThreshold(prevScale, touches.scale, SCALING_THRESHOLD)) {
scale = origScale * touches.scale;
prevScale = touches.scale;
}
}
update();
});
});
Object.freeze(self);
return self;
};
Lens.Components.Draggable = Draggable;
return Draggable;
});