Reworking click and pinch handling to get better behavior on multi-touch devices. r=ahocevar +testing from others (closes #3133)
git-svn-id: http://svn.openlayers.org/trunk/openlayers@11695 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
@@ -51,13 +51,21 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* constructed.
|
||||
*/
|
||||
pixelTolerance: 0,
|
||||
|
||||
|
||||
/**
|
||||
* APIProperty: dblclickTolerance
|
||||
* {Number} Maximum distance in pixels between clicks for a sequence of
|
||||
* events to be considered a double click. Default is 13. If the
|
||||
* distance between two clicks is greater than this value, a double-
|
||||
* click will not be fired.
|
||||
*/
|
||||
dblclickTolerance: 13,
|
||||
|
||||
/**
|
||||
* APIProperty: stopSingle
|
||||
* {Boolean} Stop other listeners from being notified of clicks. Default
|
||||
* is false. If true, any click listeners registered before this one
|
||||
* will not be notified of *any* click event (associated with double
|
||||
* or single clicks).
|
||||
* is false. If true, any listeners registered before this one for
|
||||
* click or rightclick events will not be notified.
|
||||
*/
|
||||
stopSingle: false,
|
||||
|
||||
@@ -82,6 +90,13 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* {Number} The id of the timeout waiting to clear the <delayedCall>.
|
||||
*/
|
||||
timerId: null,
|
||||
|
||||
/**
|
||||
* Property: touch
|
||||
* {Boolean} When a touchstart event is fired, touch will be true and all
|
||||
* mouse related listeners will do nothing.
|
||||
*/
|
||||
touch: false,
|
||||
|
||||
/**
|
||||
* Property: down
|
||||
@@ -92,15 +107,22 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
down: null,
|
||||
|
||||
/**
|
||||
/**
|
||||
* Property: last
|
||||
* {Object} Object that store relevant information about the last
|
||||
* touchmove. Its 'xy' OpenLayers.Pixel property gives
|
||||
* mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
|
||||
* the average location of the mouse/touch event. Its 'touches'
|
||||
* property records clientX/clientY of each touches.
|
||||
*/
|
||||
last: null,
|
||||
|
||||
/**
|
||||
* Property: first
|
||||
* {Object} When waiting for double clicks, this object will store
|
||||
* information about the first click in a two click sequence.
|
||||
*/
|
||||
first: null,
|
||||
|
||||
/**
|
||||
* Property: rightclickTimerId
|
||||
* {Number} The id of the right mouse timeout waiting to clear the
|
||||
@@ -126,25 +148,8 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
initialize: function(control, callbacks, options) {
|
||||
OpenLayers.Handler.prototype.initialize.apply(this, arguments);
|
||||
// optionally register for mousedown
|
||||
if(this.pixelTolerance != null) {
|
||||
this.mousedown = function(evt) {
|
||||
this.down = this.getEventInfo(evt);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: mousedown
|
||||
* Handle mousedown. Only registered as a listener if pixelTolerance is
|
||||
* a non-zero value at construction.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
mousedown: null,
|
||||
|
||||
/**
|
||||
* Method: touchstart
|
||||
* Handle touchstart.
|
||||
@@ -153,8 +158,67 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
touchstart: function(evt) {
|
||||
if (!this.touch) {
|
||||
this.unregisterMouseListeners();
|
||||
this.touch = true;
|
||||
}
|
||||
this.down = this.getEventInfo(evt);
|
||||
this.last = null;
|
||||
this.last = this.getEventInfo(evt);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchmove
|
||||
* Store position of last move, because touchend event can have
|
||||
* an empty "touches" property.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
touchmove: function(evt) {
|
||||
this.last = this.getEventInfo(evt);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchend
|
||||
* Correctly set event xy property, and add lastTouches to have
|
||||
* touches property from last touchstart or touchmove
|
||||
*/
|
||||
touchend: function(evt) {
|
||||
// touchstart may not have been allowed to propagate
|
||||
if (this.down) {
|
||||
evt.xy = this.last.xy;
|
||||
evt.lastTouches = this.last.touches;
|
||||
this.handleSingle(evt);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: unregisterMouseListeners
|
||||
* In a touch environment, we don't want to handle mouse events.
|
||||
*/
|
||||
unregisterMouseListeners: function() {
|
||||
this.map.events.un({
|
||||
mousedown: this.mousedown,
|
||||
mouseup: this.mouseup,
|
||||
click: this.click,
|
||||
dblclick: this.dblclick,
|
||||
scope: this
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: mousedown
|
||||
* Handle mousedown.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
mousedown: function(evt) {
|
||||
this.down = this.getEventInfo(evt);
|
||||
this.last = this.getEventInfo(evt);
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -171,8 +235,7 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
// Collect right mouse clicks from the mouseup
|
||||
// IE - ignores the second right click in mousedown so using
|
||||
// mouseup instead
|
||||
if(this.checkModifiers(evt) &&
|
||||
this.control.handleRightClicks &&
|
||||
if (this.checkModifiers(evt) && this.control.handleRightClicks &&
|
||||
OpenLayers.Event.isRightClick(evt)) {
|
||||
propagate = this.rightclick(evt);
|
||||
}
|
||||
@@ -194,7 +257,7 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
if(this.rightclickTimerId != null) {
|
||||
//Second click received before timeout this must be
|
||||
// a double click
|
||||
this.clearTimer();
|
||||
this.clearTimer();
|
||||
this.callback('dblrightclick', [evt]);
|
||||
return !this.stopDouble;
|
||||
} else {
|
||||
@@ -227,93 +290,93 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
if (evt) {
|
||||
this.callback('rightclick', [evt]);
|
||||
}
|
||||
return !this.stopSingle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: dblclick
|
||||
* Handle dblclick. For a dblclick, we get two clicks in some browsers
|
||||
* (FF) and one in others (IE). So we need to always register for
|
||||
* dblclick to properly handle single clicks.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
dblclick: function(evt) {
|
||||
// for touch devices trigger dblclick only for
|
||||
// "one finger" touch
|
||||
var last = this.down || this.last;
|
||||
if (this.passesTolerance(evt) &&
|
||||
(!last || !last.touches || last.touches.length == 1)) {
|
||||
if(this["double"]) {
|
||||
this.callback('dblclick', [evt]);
|
||||
}
|
||||
this.clearTimer();
|
||||
}
|
||||
return !this.stopDouble;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchmove
|
||||
* Store position of last move, because touchend event can have
|
||||
* an empty "touches" property.
|
||||
*/
|
||||
touchmove: function(evt) {
|
||||
this.last = this.getEventInfo(evt);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchend
|
||||
* Correctly set event xy property, and add lastTouches to have
|
||||
* touches property from last touchstart or touchmove
|
||||
*/
|
||||
touchend: function(evt) {
|
||||
var last = this.last || this.down;
|
||||
if (!evt || !last) {
|
||||
return false;
|
||||
}
|
||||
evt.xy = last.xy;
|
||||
evt.lastTouches = last.touches;
|
||||
return evt.xy ? this.click(evt) : false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: click
|
||||
* Handle click.
|
||||
* Handle click events from the browser. This is registered as a listener
|
||||
* for click events and should not be called from other events in this
|
||||
* handler.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
click: function(evt) {
|
||||
// Sencha Touch emulates click events, see ticket 3079 for more info
|
||||
if (this.down && this.down.touches && evt.type === "click") {
|
||||
return !this.stopSingle;
|
||||
if (!this.last) {
|
||||
this.last = this.getEventInfo(evt);
|
||||
}
|
||||
if(this.passesTolerance(evt)) {
|
||||
if(this.timerId != null) {
|
||||
this.handleSingle(evt);
|
||||
return !this.stopSingle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: dblclick
|
||||
* Handle dblclick. For a dblclick, we get two clicks in some browsers
|
||||
* (FF) and one in others (IE). So we need to always register for
|
||||
* dblclick to properly handle single clicks. This method is registered
|
||||
* as a listener for the dblclick browser event. It should *not* be
|
||||
* called by other methods in this handler.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} Continue propagating this event.
|
||||
*/
|
||||
dblclick: function(evt) {
|
||||
this.handleDouble(evt);
|
||||
return !this.stopDouble;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: handleDouble
|
||||
* Handle double-click sequence.
|
||||
*/
|
||||
handleDouble: function(evt) {
|
||||
if (this["double"] && this.passesDblclickTolerance(evt)) {
|
||||
this.callback("dblclick", [evt]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: handleSingle
|
||||
* Handle single click sequence.
|
||||
*/
|
||||
handleSingle: function(evt) {
|
||||
if (this.passesTolerance(evt)) {
|
||||
if (this.timerId != null) {
|
||||
// already received a click
|
||||
var last = this.down || this.last;
|
||||
if (last && last.touches && last.touches.length > 0) {
|
||||
// touch device - we may trigger dblclick
|
||||
this.dblclick(evt);
|
||||
} else {
|
||||
if (this.last.touches && this.last.touches.length === 1) {
|
||||
// touch device, no dblclick event - this may be a double
|
||||
this.handleDouble(evt);
|
||||
}
|
||||
// if we're not in a touch environment we clear the click timer
|
||||
// if we've got a second touch, we'll get two touchend events
|
||||
if (!this.last.touches || this.last.touches.length !== 2) {
|
||||
this.clearTimer();
|
||||
}
|
||||
} else {
|
||||
// remember the first click info so we can compare to the second
|
||||
this.first = this.getEventInfo(evt);
|
||||
// set the timer, send evt only if single is true
|
||||
//use a clone of the event object because it will no longer
|
||||
//be a valid event object in IE in the timer callback
|
||||
var clickEvent = this.single ?
|
||||
OpenLayers.Util.extend({}, evt) : null;
|
||||
this.timerId = window.setTimeout(
|
||||
OpenLayers.Function.bind(this.delayedCall, this, clickEvent),
|
||||
this.delay
|
||||
);
|
||||
this.queuePotentialClick(clickEvent);
|
||||
}
|
||||
}
|
||||
return !this.stopSingle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: queuePotentialClick
|
||||
* This method is separated out largely to make testing easier (so we
|
||||
* don't have to override window.setTimeout)
|
||||
*/
|
||||
queuePotentialClick: function(evt) {
|
||||
this.timerId = window.setTimeout(
|
||||
OpenLayers.Function.bind(this.delayedCall, this, evt),
|
||||
this.delay
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: passesTolerance
|
||||
* Determine whether the event is within the optional pixel tolerance. Note
|
||||
@@ -327,28 +390,67 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
passesTolerance: function(evt) {
|
||||
var passes = true;
|
||||
if(this.pixelTolerance != null && this.down && this.down.xy) {
|
||||
var dpx = Math.sqrt(
|
||||
Math.pow(this.down.xy.x - evt.xy.x, 2) +
|
||||
Math.pow(this.down.xy.y - evt.xy.y, 2)
|
||||
);
|
||||
if(dpx > this.pixelTolerance) {
|
||||
passes = false;
|
||||
if (this.pixelTolerance != null && this.down && this.down.xy) {
|
||||
passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
|
||||
// for touch environments, we also enforce that all touches
|
||||
// start and end within the given tolerance to be considered a click
|
||||
if (passes && this.touch &&
|
||||
this.down.touches.length === this.last.touches.length) {
|
||||
// the touchend event doesn't come with touches, so we check
|
||||
// down and last
|
||||
for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
|
||||
if (this.getTouchDistance(
|
||||
this.down.touches[i],
|
||||
this.last.touches[i]
|
||||
) > this.pixelTolerance) {
|
||||
passes = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return passes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: getTouchDistance
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} The pixel displacement between two touches.
|
||||
*/
|
||||
getTouchDistance: function(from, to) {
|
||||
return Math.sqrt(
|
||||
Math.pow(from.clientX - to.clientX, 2) +
|
||||
Math.pow(from.clientY - to.clientY, 2)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: passesDblclickTolerance
|
||||
* Determine whether the event is within the optional double-cick pixel
|
||||
* tolerance.
|
||||
*
|
||||
* Returns:
|
||||
* {Boolean} The click is within the double-click pixel tolerance.
|
||||
*/
|
||||
passesDblclickTolerance: function(evt) {
|
||||
var passes = true;
|
||||
if (this.down && this.first) {
|
||||
passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
|
||||
}
|
||||
return passes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: clearTimer
|
||||
* Clear the timer and set <timerId> to null.
|
||||
*/
|
||||
clearTimer: function() {
|
||||
if(this.timerId != null) {
|
||||
if (this.timerId != null) {
|
||||
window.clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
}
|
||||
if(this.rightclickTimerId != null) {
|
||||
if (this.rightclickTimerId != null) {
|
||||
window.clearTimeout(this.rightclickTimerId);
|
||||
this.rightclickTimerId = null;
|
||||
}
|
||||
@@ -361,8 +463,8 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
delayedCall: function(evt) {
|
||||
this.timerId = null;
|
||||
if(evt) {
|
||||
this.callback('click', [evt]);
|
||||
if (evt) {
|
||||
this.callback("click", [evt]);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -407,7 +509,7 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
|
||||
this.clearTimer();
|
||||
this.down = null;
|
||||
this.last = null;
|
||||
this.first = null;
|
||||
deactivated = true;
|
||||
}
|
||||
return deactivated;
|
||||
|
||||
@@ -37,9 +37,9 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
||||
/**
|
||||
* Property: stopDown
|
||||
* {Boolean} Stop propagation of touchstart events from getting to
|
||||
* listeners on the same element. Default is true.
|
||||
* listeners on the same element. Default is false.
|
||||
*/
|
||||
stopDown: true,
|
||||
stopDown: false,
|
||||
|
||||
/**
|
||||
* Property: pinching
|
||||
@@ -105,6 +105,7 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
||||
this.start = null;
|
||||
this.last = null;
|
||||
}
|
||||
// prevent document dragging
|
||||
OpenLayers.Event.stop(evt);
|
||||
return propagate;
|
||||
},
|
||||
@@ -120,15 +121,15 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* {Boolean} Let the event propagate.
|
||||
*/
|
||||
touchmove: function(evt) {
|
||||
var propagate = true;
|
||||
if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
|
||||
this.pinching = true;
|
||||
var current = this.getPinchData(evt);
|
||||
this.callback("move", [evt, current]);
|
||||
this.last = current;
|
||||
propagate = false;
|
||||
// prevent document dragging
|
||||
OpenLayers.Event.stop(evt);
|
||||
}
|
||||
return propagate;
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -142,16 +143,14 @@ OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* {Boolean} Let the event propagate.
|
||||
*/
|
||||
touchend: function(evt) {
|
||||
var propagate = true;
|
||||
if (this.started) {
|
||||
this.started = false;
|
||||
this.pinching = false;
|
||||
this.callback("done", [evt, this.start, this.last]);
|
||||
this.start = null;
|
||||
this.last = null;
|
||||
propagate = false;
|
||||
}
|
||||
return propagate;
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user