Ext.gesture.Manager = new Ext.AbstractManager({
startEventName: 'touchstart',
moveEventName: 'touchmove',
endEventName: 'touchend',
init: function() {
this.targets = [];
if (!Ext.supports.Touch) {
Ext.apply(this, {
startEventName: 'mousedown',
moveEventName: 'mousemove',
endEventName: 'mouseup'
});
}
this.followTouches = [];
this.currentGestures = [];
this.currentTargets = [];
document.addEventListener(this.startEventName, Ext.createDelegate(this.onTouchStart, this), true);
document.addEventListener(this.endEventName, Ext.createDelegate(this.onTouchEnd, this), true);
},
onTouchStart: function(e) {
// There's already a touchstart without any touchend!
// This used to happen on HTC Desire and HTC Incredible
// We have to clean it up
if (this.startEvent) {
this.onTouchEnd(e);
}
var targets = [],
target = e.target;
this.locks = {};
this.currentTargets = [target];
while (target) {
if (this.targets.indexOf(target) != -1) {
targets.unshift(target);
}
target = target.parentNode;
this.currentTargets.push(target);
}
this.startEvent = e;
this.startPoint = Ext.util.Point.fromEvent(e);
this.handleTargets(targets, e);
},
/**
* This listener is here to always ensure we stop all current gestures
* @private
*/
onTouchEnd: function(e) {
var gestures = this.currentGestures.slice(0),
ln = gestures.length,
i, gesture, endPoint,
hasMoved = false,
touch = e.changedTouches ? e.changedTouches[0] : e;
if (this.startPoint) {
endPoint = Ext.util.Point.fromEvent(e);
// The point has changed, we should execute another onTouchMove before onTouchEnd
// to deal with the problem of missing events on Androids and alike
// This significantly improves scrolling experience on Androids! Yeah!
if (!this.startPoint.equals(endPoint)) {
hasMoved = true;
}
}
this.followTouches = [];
this.startedChangedTouch = false;
this.currentTargets = [];
this.startEvent = null;
this.startPoint = null;
for (i = 0; i < ln; i++) {
gesture = gestures[i];
if (!e.stopped && gesture.listenForEnd) {
if (hasMoved) {
gesture.onTouchMove(e, touch);
}
gesture.onTouchEnd(e, touch);
}
this.stopGesture(gesture);
}
},
startGesture: function(gesture) {
var me = this;
gesture.started = true;
if (gesture.listenForMove) {
gesture.onTouchMoveWrap = function(e) {
if (!e.stopped) {
gesture.onTouchMove(e, e.changedTouches ? e.changedTouches[0] : e);
}
};
gesture.target.addEventListener(me.moveEventName, gesture.onTouchMoveWrap, !!gesture.capture);
}
this.currentGestures.push(gesture);
},
stopGesture: function(gesture) {
gesture.started = false;
if (gesture.listenForMove) {
gesture.target.removeEventListener(this.moveEventName, gesture.onTouchMoveWrap, !!gesture.capture);
}
this.currentGestures.remove(gesture);
},
handleTargets: function(targets, e) {
// In handle targets we have to first handle all the capture targets,
// then all the bubble targets.
var ln = targets.length,
i, target;
this.startedChangedTouch = false;
this.startedTouches = Ext.supports.Touch ? e.touches : [e];
for (i = 0; i < ln; i++) {
if (e.stopped) {
break;
}
target = targets[i];
this.handleTarget(target, e, true);
}
for (i = ln - 1; i >= 0; i--) {
if (e.stopped) {
break;
}
target = targets[i];
this.handleTarget(target, e, false);
}
if (this.startedChangedTouch) {
this.followTouches = this.followTouches.concat((Ext.supports.Touch && e.targetTouches) ? Ext.toArray(e.targetTouches) : [e]);
}
},
handleTarget: function(target, e, capture) {
var gestures = Ext.Element.data(target, 'x-gestures') || [],
ln = gestures.length,
i, gesture;
for (i = 0; i < ln; i++) {
gesture = gestures[i];
if (
(!!gesture.capture === !!capture) &&
(this.followTouches.length < gesture.touches) &&
((Ext.supports.Touch && e.targetTouches) ? (e.targetTouches.length === gesture.touches) : true)
) {
this.startedChangedTouch = true;
this.startGesture(gesture);
if (gesture.listenForStart) {
gesture.onTouchStart(e, e.changedTouches ? e.changedTouches[0] : e);
}
if (e.stopped) {
break;
}
}
}
},
addEventListener: function(target, eventName, listener, options) {
target = Ext.getDom(target);
var targets = this.targets,
name = this.getGestureName(eventName),
gestures = Ext.Element.data(target, 'x-gestures') || [],
gesture;
//
if (!name) {
throw new Error('Trying to subscribe to unknown event ' + eventName);
}
//
if (targets.indexOf(target) == -1) {
this.targets.push(target);
}
gesture = this.get(target.id + '-' + name);
if (!gesture) {
gesture = this.create(Ext.apply({}, options || {}, {
target: target,
type: name
}));
gestures.push(gesture);
Ext.Element.data(target, 'x-gestures', gestures);
}
gesture.addListener(eventName, listener);
// If there is already a finger down, then instantly start the gesture
if (this.startedChangedTouch && this.currentTargets.contains(target) && !gesture.started) {
this.startGesture(gesture);
if (gesture.listenForStart) {
gesture.onTouchStart(this.startEvent, this.startedTouches[0]);
}
}
},
removeEventListener: function(target, eventName, listener) {
target = Ext.getDom(target);
var name = this.getGestureName(eventName),
gestures = Ext.Element.data(target, 'x-gestures') || [],
gesture;
gesture = this.get(target.id + '-' + name);
if (gesture) {
gesture.removeListener(eventName, listener);
for (name in gesture.listeners) {
return;
}
gesture.destroy();
gestures.remove(gesture);
Ext.Element.data(target, 'x-gestures', gestures);
}
},
getGestureName: function(ename) {
return this.names && this.names[ename];
},
registerType: function(type, cls) {
var handles = cls.prototype.handles,
i, ln;
this.types[type] = cls;
cls[this.typeName] = type;
if (!handles) {
handles = cls.prototype.handles = [type];
}
this.names = this.names || {};
for (i = 0, ln = handles.length; i < ln; i++) {
this.names[handles[i]] = type;
}
}
});
Ext.regGesture = function() {
return Ext.gesture.Manager.registerType.apply(Ext.gesture.Manager, arguments);
};