blob: 2b551e7e3b696f4659a22dde77ec4673b5b7e64d [file] [log] [blame]
class GestureRecognizer
{
constructor(target = null, delegate = null)
{
this._targetTouches = [];
this.modifierKeys = {
alt : false,
ctrl : false,
meta : false,
shift : false
};
this._state = GestureRecognizer.States.Possible;
this._enabled = true;
this.target = target;
this.delegate = delegate;
}
// Public
get state()
{
return this._state;
}
set state(state)
{
if (this._state === state && state !== GestureRecognizer.States.Changed)
return;
this._state = state;
if (this.delegate && typeof this.delegate.gestureRecognizerStateDidChange === "function")
this.delegate.gestureRecognizerStateDidChange(this);
}
get target()
{
return this._target;
}
set target(target)
{
if (!target || this._target === target)
return;
this._target = target;
this._initRecognizer();
}
get numberOfTouches()
{
return this._targetTouches.length;
}
get enabled()
{
return this._enabled;
}
set enabled(enabled)
{
if (this._enabled === enabled)
return;
this._enabled = enabled;
if (!enabled) {
if (this.numberOfTouches === 0) {
this._removeTrackingListeners();
this.reset();
} else
this.enterCancelledState();
}
this._updateBaseListeners();
}
reset()
{
// Implemented by subclasses.
}
locationInElement(element)
{
const p = new DOMPoint;
const touches = this._targetTouches;
const count = touches.length;
for (let i = 0; i < count; ++i) {
const touch = touches[i];
p.x += touch.pageX;
p.y += touch.pageY;
}
p.x /= count;
p.y /= count;
if (!element)
return p;
// FIXME: are WebKitPoint and DOMPoint interchangeable?
const wkPoint = window.webkitConvertPointFromPageToNode(element, new WebKitPoint(p.x, p.y));
return new DOMPoint(wkPoint.x, wkPoint.y);
}
locationInClient()
{
const p = new DOMPoint;
const touches = this._targetTouches;
const count = touches.length;
for (let i = 0; i < count; ++i) {
const touch = touches[i];
p.x += touch.clientX;
p.y += touch.clientY;
}
p.x /= count;
p.y /= count;
return p;
}
locationOfTouchInElement(touchIndex, element)
{
const touch = this._targetTouches[touchIndex];
if (!touch)
return new DOMPoint;
const touchLocation = new DOMPoint(touch.pageX, touch.pageY);
if (!element)
return touchLocation;
// FIXME: are WebKitPoint and DOMPoint interchangeable?
const wkPoint = window.webkitConvertPointFromPageToNode(element, new WebKitPoint(touchLocation.x, touchLocation.y));
return new DOMPoint(wkPoint.x, wkPoint.y);
}
touchesBegan(event)
{
if (event.currentTarget !== this._target)
return;
window.addEventListener(GestureRecognizer.Events.TouchMove, this, true);
window.addEventListener(GestureRecognizer.Events.TouchEnd, this, true);
window.addEventListener(GestureRecognizer.Events.TouchCancel, this, true);
this.enterPossibleState();
}
touchesMoved(event)
{
// Implemented by subclasses.
}
touchesEnded(event)
{
// Implemented by subclasses.
}
touchesCancelled(event)
{
// Implemented by subclasses.
}
gestureBegan(event)
{
if (event.currentTarget !== this._target)
return;
this._target.addEventListener(GestureRecognizer.Events.GestureChange, this, true);
this._target.addEventListener(GestureRecognizer.Events.GestureEnd, this, true);
this.enterPossibleState();
}
gestureChanged(event)
{
// Implemented by subclasses.
}
gestureEnded(event)
{
// Implemented by subclasses.
}
enterPossibleState()
{
this.state = GestureRecognizer.States.Possible;
}
enterBeganState()
{
if (this.delegate && typeof this.delegate.gestureRecognizerShouldBegin === "function" && !this.delegate.gestureRecognizerShouldBegin(this)) {
this.enterFailedState();
return;
}
this.state = GestureRecognizer.States.Began;
}
enterEndedState()
{
this.state = GestureRecognizer.States.Ended;
this._removeTrackingListeners();
this.reset();
}
enterCancelledState()
{
this.state = GestureRecognizer.States.Cancelled;
this._removeTrackingListeners();
this.reset();
}
enterFailedState()
{
this.state = GestureRecognizer.States.Failed;
this._removeTrackingListeners();
this.reset();
}
enterChangedState()
{
this.state = GestureRecognizer.States.Changed;
}
enterRecognizedState()
{
this.state = GestureRecognizer.States.Recognized;
}
// Protected
handleEvent(event)
{
this._updateTargetTouches(event);
this._updateKeyboardModifiers(event);
switch (event.type) {
case GestureRecognizer.Events.TouchStart:
this.touchesBegan(event);
break;
case GestureRecognizer.Events.TouchMove:
this.touchesMoved(event);
break;
case GestureRecognizer.Events.TouchEnd:
this.touchesEnded(event);
break;
case GestureRecognizer.Events.TouchCancel:
this.touchesCancelled(event);
break;
case GestureRecognizer.Events.GestureStart:
this.gestureBegan(event);
break;
case GestureRecognizer.Events.GestureChange:
this.gestureChanged(event);
break;
case GestureRecognizer.Events.GestureEnd:
this.gestureEnded(event);
break;
}
}
// Private
_initRecognizer()
{
this.reset();
this.state = GestureRecognizer.States.Possible;
this._updateBaseListeners();
}
_updateBaseListeners()
{
if (!this._target)
return;
if (this._enabled) {
this._target.addEventListener(GestureRecognizer.Events.TouchStart, this);
if (GestureRecognizer.SupportsGestures)
this._target.addEventListener(GestureRecognizer.Events.GestureStart, this);
} else {
this._target.removeEventListener(GestureRecognizer.Events.TouchStart, this);
if (GestureRecognizer.SupportsGestures)
this._target.removeEventListener(GestureRecognizer.Events.GestureStart, this);
}
}
_removeTrackingListeners()
{
window.removeEventListener(GestureRecognizer.Events.TouchMove, this, true);
window.removeEventListener(GestureRecognizer.Events.TouchEnd, this, true);
this._target.removeEventListener(GestureRecognizer.Events.GestureChange, this, true);
this._target.removeEventListener(GestureRecognizer.Events.GestureEnd, this, true);
}
_updateTargetTouches(event)
{
if (!GestureRecognizer.SupportsTouches) {
if (event.type === GestureRecognizer.Events.TouchEnd)
this._targetTouches = [];
else
this._targetTouches = [event];
return;
}
if (!(event instanceof TouchEvent))
return;
// With a touchstart event, event.targetTouches is accurate so
// we simply add all of those.
if (event.type === GestureRecognizer.Events.TouchStart) {
this._targetTouches = [];
let touches = event.targetTouches;
for (let i = 0, count = touches.length; i < count; ++i)
this._targetTouches.push(touches[i]);
return;
}
// With a touchmove event, the target is window so event.targetTouches is
// inaccurate so we add all touches that we knew about previously.
if (event.type === GestureRecognizer.Events.TouchMove) {
let targetIdentifiers = this._targetTouches.map(function(touch) {
return touch.identifier;
});
this._targetTouches = [];
let touches = event.touches;
for (let i = 0, count = touches.length; i < count; ++i) {
let touch = touches[i];
if (targetIdentifiers.indexOf(touch.identifier) !== -1)
this._targetTouches.push(touch);
}
return;
}
// With a touchend or touchcancel event, we only keep the existing touches
// that are also found in event.touches.
let allTouches = event.touches;
let existingIdentifiers = [];
for (let i = 0, count = allTouches.length; i < count; ++i)
existingIdentifiers.push(allTouches[i].identifier);
this._targetTouches = this._targetTouches.filter(function(touch) {
return existingIdentifiers.indexOf(touch.identifier) !== -1;
});
}
_updateKeyboardModifiers(event)
{
this.modifierKeys.alt = event.altKey;
this.modifierKeys.ctrl = event.ctrlKey;
this.modifierKeys.meta = event.metaKey;
this.modifierKeys.shift = event.shiftKey;
}
}
GestureRecognizer.SupportsTouches = "createTouch" in document;
GestureRecognizer.SupportsGestures = !!window.GestureEvent;
GestureRecognizer.States = {
Possible : "possible",
Began : "began",
Changed : "changed",
Ended : "ended",
Cancelled : "cancelled",
Failed : "failed",
Recognized : "ended"
};
GestureRecognizer.Events = {
TouchStart : GestureRecognizer.SupportsTouches ? "touchstart" : "mousedown",
TouchMove : GestureRecognizer.SupportsTouches ? "touchmove" : "mousemove",
TouchEnd : GestureRecognizer.SupportsTouches ? "touchend" : "mouseup",
TouchCancel : "touchcancel",
GestureStart : "gesturestart",
GestureChange : "gesturechange",
GestureEnd : "gestureend"
};