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:
@@ -124,6 +124,7 @@ OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
pinchStart: function(evt, pinchData) {
|
||||
this.pinchOrigin = evt.xy;
|
||||
this.currentCenter = evt.xy;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -170,7 +171,9 @@ OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
|
||||
* of the pinch gesture. This give us the final scale of the pinch.
|
||||
*/
|
||||
pinchDone: function(evt, start, last) {
|
||||
this.applyTransform("");
|
||||
var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
|
||||
if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
|
||||
var resolution = this.map.getResolutionForZoom(zoom);
|
||||
|
||||
var location = this.map.getLonLatFromPixel(this.pinchOrigin);
|
||||
@@ -181,10 +184,7 @@ OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
|
||||
location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
|
||||
|
||||
this.map.setCenter(location, zoom);
|
||||
|
||||
var style = this.map.layerContainerDiv.style;
|
||||
style['-webkit-transform'] = "";
|
||||
style['-moz-transform'] = "";
|
||||
}
|
||||
},
|
||||
|
||||
CLASS_NAME: "OpenLayers.Control.PinchZoom"
|
||||
|
||||
@@ -44,6 +44,12 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
pinchZoomOptions: null,
|
||||
|
||||
/**
|
||||
* APIProperty: clickHandlerOptions
|
||||
* {Object} Options passed to the Click handler.
|
||||
*/
|
||||
clickHandlerOptions: null,
|
||||
|
||||
/**
|
||||
* APIProperty: documentDrag
|
||||
* {Boolean} Allow panning of the map by dragging outside map viewport.
|
||||
@@ -121,13 +127,14 @@ OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
|
||||
*/
|
||||
draw: function() {
|
||||
var clickCallbacks = {
|
||||
'click': this.defaultClick,
|
||||
'dblclick': this.defaultDblClick
|
||||
};
|
||||
var clickOptions = {
|
||||
'double': true,
|
||||
'stopDouble': true
|
||||
click: this.defaultClick,
|
||||
dblclick: this.defaultDblClick
|
||||
};
|
||||
var clickOptions = OpenLayers.Util.extend({
|
||||
"double": true,
|
||||
stopDouble: true,
|
||||
pixelTolerance: 2
|
||||
}, this.clickHandlerOptions);
|
||||
this.handlers.click = new OpenLayers.Handler.Click(
|
||||
this, clickCallbacks, clickOptions
|
||||
);
|
||||
|
||||
@@ -52,12 +52,20 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
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,
|
||||
|
||||
@@ -83,6 +91,13 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
*/
|
||||
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
|
||||
* {Object} Object that store relevant information about the last
|
||||
@@ -95,12 +110,19 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
@@ -227,6 +290,22 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
if (evt) {
|
||||
this.callback('rightclick', [evt]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: 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) {
|
||||
if (!this.last) {
|
||||
this.last = this.getEventInfo(evt);
|
||||
}
|
||||
this.handleSingle(evt);
|
||||
return !this.stopSingle;
|
||||
},
|
||||
|
||||
@@ -234,84 +313,68 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
* 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.
|
||||
* 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) {
|
||||
// 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();
|
||||
}
|
||||
this.handleDouble(evt);
|
||||
return !this.stopDouble;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchmove
|
||||
* Store position of last move, because touchend event can have
|
||||
* an empty "touches" property.
|
||||
* Method: handleDouble
|
||||
* Handle double-click sequence.
|
||||
*/
|
||||
touchmove: function(evt) {
|
||||
this.last = this.getEventInfo(evt);
|
||||
handleDouble: function(evt) {
|
||||
if (this["double"] && this.passesDblclickTolerance(evt)) {
|
||||
this.callback("dblclick", [evt]);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Method: touchend
|
||||
* Correctly set event xy property, and add lastTouches to have
|
||||
* touches property from last touchstart or touchmove
|
||||
* Method: handleSingle
|
||||
* Handle single click sequence.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
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.queuePotentialClick(clickEvent);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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, clickEvent),
|
||||
OpenLayers.Function.bind(this.delayedCall, this, evt),
|
||||
this.delay
|
||||
);
|
||||
}
|
||||
}
|
||||
return !this.stopSingle;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -328,14 +391,53 @@ 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 = 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;
|
||||
},
|
||||
|
||||
@@ -362,7 +464,7 @@ OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
|
||||
delayedCall: function(evt) {
|
||||
this.timerId = null;
|
||||
if (evt) {
|
||||
this.callback('click', [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;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,6 +102,29 @@
|
||||
|
||||
}
|
||||
|
||||
function test_clickHandlerOptions(t) {
|
||||
|
||||
t.plan(3);
|
||||
|
||||
var nav = new OpenLayers.Control.TouchNavigation();
|
||||
t.eq(nav.clickHandlerOptions, null, "clickHandlerOptions null by default");
|
||||
|
||||
var map = new OpenLayers.Map({
|
||||
div: document.body,
|
||||
controls: [
|
||||
new OpenLayers.Control.TouchNavigation({
|
||||
clickHandlerOptions: {foo: "bar"}
|
||||
})
|
||||
]
|
||||
});
|
||||
nav = map.controls[0];
|
||||
|
||||
t.eq(nav.handlers.click.foo, "bar", "foo property is set on the click handler");
|
||||
t.eq(nav.handlers.click.pixelTolerance, 2, "pixelTolerance is 2 by default");
|
||||
map.destroy();
|
||||
|
||||
}
|
||||
|
||||
function test_zoomOut(t) {
|
||||
t.plan(1);
|
||||
|
||||
@@ -114,7 +137,7 @@
|
||||
var control = new OpenLayers.Control.TouchNavigation();
|
||||
map.addControl(control);
|
||||
var handler = control.handlers.click;
|
||||
handler.touchstart({xy: {x: 1, y: 1}, touches: ["foo", "bar"]});
|
||||
handler.touchstart({xy: new OpenLayers.Pixel(1 ,1), touches: ["foo", "bar"]});
|
||||
handler.touchend({});
|
||||
t.delay_call(1, function() {
|
||||
t.eq(map.getZoom(), 4, "Did we zoom out?");
|
||||
|
||||
@@ -102,11 +102,15 @@
|
||||
|
||||
var callbackMap;
|
||||
function callbackSetup(log, options) {
|
||||
callbackMap = new OpenLayers.Map('map', {controls: []});
|
||||
|
||||
var control = {
|
||||
map: callbackMap
|
||||
};
|
||||
callbackMap = new OpenLayers.Map({
|
||||
div: "map",
|
||||
controls: [], // no controls here because these tests use a custom setTimeout and we only want setTimeout calls from a single handler
|
||||
layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
|
||||
center: new OpenLayers.LonLat(0, 0),
|
||||
zoom: 1
|
||||
});
|
||||
var control = new OpenLayers.Control();
|
||||
callbackMap.addControl(control);
|
||||
|
||||
var callbacks = {
|
||||
"click": function(evt) {
|
||||
@@ -119,6 +123,7 @@
|
||||
var handler = new OpenLayers.Handler.Click(control, callbacks, options);
|
||||
handler.activate();
|
||||
|
||||
|
||||
var timers = {};
|
||||
window._setTimeout = window.setTimeout;
|
||||
window.setTimeout = function(func, delay) {
|
||||
@@ -270,13 +275,140 @@
|
||||
|
||||
handler.map.events.triggerEvent("mouseup", up);
|
||||
handler.map.events.triggerEvent("click", up);
|
||||
|
||||
t.eq(log.length, 0, "nothing logged - event outside tolerance");
|
||||
|
||||
callbackTeardown();
|
||||
|
||||
}
|
||||
|
||||
function test_callbacks_within_dblclickTolerance(t) {
|
||||
t.plan(6);
|
||||
|
||||
var log = [];
|
||||
var handler = callbackSetup(log, {single: false, "double": true, dblclickTolerance: 8});
|
||||
|
||||
var first = {
|
||||
xy: px(0, 0)
|
||||
};
|
||||
var second = {
|
||||
xy: px(0, 5)
|
||||
};
|
||||
|
||||
handler.map.events.triggerEvent("mousedown", first);
|
||||
handler.map.events.triggerEvent("mouseup", first);
|
||||
handler.map.events.triggerEvent("click", first);
|
||||
t.eq(log.length, 1, "one item logged");
|
||||
t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
|
||||
|
||||
handler.map.events.triggerEvent("mousedown", second);
|
||||
handler.map.events.triggerEvent("mouseup", second);
|
||||
handler.map.events.triggerEvent("click", second);
|
||||
t.eq(log.length, 2, "two events logged");
|
||||
t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
|
||||
|
||||
handler.map.events.triggerEvent("dblclick", second);
|
||||
t.eq(log.length, 3, "three items logged");
|
||||
t.eq(log[2] && log[2].callback, "dblclick", "dblclick callback called");
|
||||
|
||||
callbackTeardown();
|
||||
}
|
||||
|
||||
function test_callbacks_outside_dblclickTolerance(t) {
|
||||
t.plan(5);
|
||||
|
||||
var log = [];
|
||||
// default dblclickTolerance is 13
|
||||
var handler = callbackSetup(log, {single: false, "double": true});
|
||||
|
||||
var first = {
|
||||
xy: px(0, 0)
|
||||
};
|
||||
var second = {
|
||||
xy: px(13.5, 0)
|
||||
};
|
||||
|
||||
handler.map.events.triggerEvent("mousedown", first);
|
||||
handler.map.events.triggerEvent("mouseup", first);
|
||||
handler.map.events.triggerEvent("click", first);
|
||||
t.eq(log.length, 1, "one item logged");
|
||||
t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
|
||||
|
||||
handler.map.events.triggerEvent("mousedown", second);
|
||||
handler.map.events.triggerEvent("mouseup", second);
|
||||
handler.map.events.triggerEvent("click", second);
|
||||
t.eq(log.length, 2, "two items logged");
|
||||
t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
|
||||
|
||||
handler.map.events.triggerEvent("dblclick", second);
|
||||
t.eq(log.length, 2, "still two items logged - dblclick callback is not called");
|
||||
|
||||
callbackTeardown();
|
||||
}
|
||||
|
||||
function test_callbacks_multitouch_single(t) {
|
||||
|
||||
t.plan(2);
|
||||
|
||||
var log = [];
|
||||
|
||||
var callbacks = {
|
||||
click: function(evt) {
|
||||
log.push({callback: "click", type: evt.type});
|
||||
},
|
||||
dblclick: function(evt) {
|
||||
log.push({callback: "dblclick", type: evt.type});
|
||||
}
|
||||
};
|
||||
|
||||
var map = new OpenLayers.Map("map");
|
||||
var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
|
||||
map.addLayer(layer);
|
||||
map.setCenter(new OpenLayers.LonLat(0, 0), 1);
|
||||
var control = new OpenLayers.Control();
|
||||
map.addControl(control);
|
||||
var handler = new OpenLayers.Handler.Click(
|
||||
control, callbacks,
|
||||
{"double": true, single: true, pixelTolerance: 2}
|
||||
);
|
||||
|
||||
// we override here so we don't have to wait for the timeout
|
||||
handler.queuePotentialClick = function(evt) {
|
||||
log.push({potential: true, evt: evt});
|
||||
OpenLayers.Handler.Click.prototype.queuePotentialClick.call(this, evt);
|
||||
}
|
||||
|
||||
handler.activate();
|
||||
|
||||
function handle(o) {
|
||||
var touches = [];
|
||||
if (("x0" in o) && ("y0" in o)) {
|
||||
touches.push({
|
||||
clientX: o.x0, clientY: o.y0
|
||||
});
|
||||
}
|
||||
if (("x1" in o) && ("y1" in o)) {
|
||||
touches.push({
|
||||
clientX: o.x1, clientY: o.y1
|
||||
});
|
||||
}
|
||||
handler.map.events.handleBrowserEvent({
|
||||
type: o.type, touches: touches
|
||||
});
|
||||
}
|
||||
|
||||
// a typical multitouch sequence goes like this:
|
||||
// touchstart, touchstart, touchend, touchend
|
||||
handle({type: "touchstart", x0: 10, y0: 10});
|
||||
handle({type: "touchstart", x0: 10, y0: 10, x1: 30, y1: 15});
|
||||
handle({type: "touchend"});
|
||||
handle({type: "touchend"});
|
||||
|
||||
t.eq(log.length, 1, "one item logged");
|
||||
t.eq(log[0] && log[0].potential, true, "click in queue - no dblclick called");
|
||||
|
||||
map.destroy();
|
||||
}
|
||||
|
||||
function test_Handler_Click_deactivate(t) {
|
||||
t.plan(4);
|
||||
var control = {
|
||||
@@ -395,83 +527,124 @@
|
||||
});
|
||||
}
|
||||
|
||||
function test_touch_ignoresimulatedclick(t) {
|
||||
t.plan(2);
|
||||
|
||||
// set up
|
||||
function test_touch_within_dblclickTolerance(t) {
|
||||
t.plan(4);
|
||||
|
||||
var log;
|
||||
|
||||
var map = new OpenLayers.Map('map');
|
||||
var control = {map: map};
|
||||
|
||||
var callbacks = {
|
||||
'dblclick': function(e) {
|
||||
log.dblclick = {x: e.xy.x, y: e.xy.y,
|
||||
lastTouches: e.lastTouches};
|
||||
click: function(evt) {
|
||||
log.push({callback: "click", type: evt.type});
|
||||
},
|
||||
dblclick: function(evt) {
|
||||
log.push({callback: "dblclick", type: evt.type});
|
||||
}
|
||||
};
|
||||
|
||||
var map = new OpenLayers.Map("map");
|
||||
var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
|
||||
map.addLayer(layer);
|
||||
map.setCenter(new OpenLayers.LonLat(0, 0), 1);
|
||||
var control = new OpenLayers.Control();
|
||||
map.addControl(control);
|
||||
var handler = new OpenLayers.Handler.Click(
|
||||
control, callbacks,
|
||||
{'double': true, pixelTolerance: null});
|
||||
{"double": true, single: true, pixelTolerance: 2}
|
||||
);
|
||||
handler.activate();
|
||||
|
||||
function handle(type, x, y) {
|
||||
map.events.handleBrowserEvent({
|
||||
type: type,
|
||||
touches: [
|
||||
{clientX: x, clientY: y}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// test
|
||||
log = [];
|
||||
// sequence of two clicks on a touch device
|
||||
// click 1
|
||||
handle("touchstart", 10, 10);
|
||||
handle("touchend", 11, 10);
|
||||
handle("mousemove", 11, 10);
|
||||
handle("mousedown", 10, 10);
|
||||
handle("mouseup", 11, 10);
|
||||
handle("click", 11, 10);
|
||||
// click 2
|
||||
handle("touchstart", 12, 10);
|
||||
handle("touchend", 12, 10);
|
||||
handle("mousedown", 12, 10);
|
||||
handle("mouseup", 12, 10);
|
||||
handle("click", 12, 10);
|
||||
|
||||
log = {};
|
||||
handler.touchstart({xy: px(1, 1), touches: ["foo"]});
|
||||
handler.touchend({});
|
||||
handler.touchstart({xy: px(1, 1), touches: ["foo"]});
|
||||
handler.touchend({type: "click"});
|
||||
|
||||
t.eq(!!handler.down.touches, true, "Handler down touches property should be truthy");
|
||||
|
||||
t.ok(log.dblclick == undefined, "dblclick callback not called with simulated click");
|
||||
t.eq(log.length, 1, "one callback called");
|
||||
t.eq(log[0] && log[0].callback, "dblclick", "click callback called");
|
||||
t.eq(log[0] && log[0].type, "touchend", "click callback called with touchend event");
|
||||
t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
|
||||
|
||||
// tear down
|
||||
map.destroy();
|
||||
}
|
||||
|
||||
function test_touch_dblclick(t) {
|
||||
t.plan(5);
|
||||
|
||||
// set up
|
||||
function test_touch_outside_dblclickTolerance(t) {
|
||||
t.plan(2);
|
||||
|
||||
var log;
|
||||
|
||||
var map = new OpenLayers.Map('map');
|
||||
var control = {map: map};
|
||||
|
||||
var callbacks = {
|
||||
'click': function(e) {
|
||||
log.click = {x: e.xy.x, y: e.xy.y,
|
||||
lastTouches: e.lastTouches};
|
||||
click: function(evt) {
|
||||
log.push({callback: "click", type: evt.type});
|
||||
},
|
||||
'dblclick': function(e) {
|
||||
log.dblclick = {x: e.xy.x, y: e.xy.y,
|
||||
lastTouches: e.lastTouches};
|
||||
dblclick: function(evt) {
|
||||
log.push({callback: "dblclick", type: evt.type});
|
||||
}
|
||||
};
|
||||
|
||||
var map = new OpenLayers.Map("map");
|
||||
var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
|
||||
map.addLayer(layer);
|
||||
map.setCenter(new OpenLayers.LonLat(0, 0), 1);
|
||||
var control = new OpenLayers.Control();
|
||||
map.addControl(control);
|
||||
var handler = new OpenLayers.Handler.Click(
|
||||
control, callbacks,
|
||||
{'double': true, pixelTolerance: null});
|
||||
{"double": true, single: true, pixelTolerance: 2, dblclickTolerance: 8}
|
||||
);
|
||||
handler.activate();
|
||||
|
||||
function handle(type, x, y) {
|
||||
var touches = [];
|
||||
if (x !== undefined && y !== undefined) {
|
||||
touches.push({
|
||||
clientX: x, clientY: y
|
||||
});
|
||||
}
|
||||
map.events.handleBrowserEvent({
|
||||
type: type, touches: touches
|
||||
});
|
||||
}
|
||||
|
||||
// test
|
||||
log = [];
|
||||
// sequence of two clicks on a touch device
|
||||
// click 1
|
||||
handle("touchstart", 10, 10);
|
||||
handle("touchend");
|
||||
handle("mousemove", 11, 10);
|
||||
handle("mousedown", 10, 10);
|
||||
handle("mouseup", 11, 10);
|
||||
handle("click", 11, 10);
|
||||
// click 2
|
||||
handle("touchstart", 20, 10);
|
||||
handle("touchend");
|
||||
handle("mousedown", 20, 10);
|
||||
handle("mouseup", 20, 10);
|
||||
handle("click", 20, 10);
|
||||
|
||||
log = {};
|
||||
handler.touchstart({xy: px(1, 1), touches: [{clientX:0, clientY:10}]});
|
||||
handler.touchend({});
|
||||
handler.touchstart({xy: px(1, 1), touches: [{clientX:0, clientY:10}]});
|
||||
handler.touchend({});
|
||||
|
||||
t.eq(log.click, undefined, "click callback not called");
|
||||
t.ok(log.dblclick != undefined, "dblclick callback called");
|
||||
if(log.dblclick != undefined) {
|
||||
t.eq(log.dblclick.x, 1, "evt.xy.x as expected");
|
||||
t.eq(log.dblclick.y, 1, "evt.xy.y as expected");
|
||||
t.ok(log.dblclick.lastTouches, "evt.lastTouches on evt");
|
||||
}
|
||||
t.eq(log.length, 0, "no callbacks called");
|
||||
t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
|
||||
|
||||
// tear down
|
||||
map.destroy();
|
||||
|
||||
Reference in New Issue
Block a user