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:
Tim Schaub
2011-03-11 01:53:26 +00:00
parent 3ce239208f
commit 5b7d530461
6 changed files with 488 additions and 184 deletions

View File

@@ -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});
control, callbacks,
{"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});
control, callbacks,
{"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();