Skip to content

Commit

Permalink
Replace inconsistently-supported dblclick events with custom handler …
Browse files Browse the repository at this point in the history
…on platforms supporting pointer events

Fix issue where double-tap-to-zoom would not be properly disabled when drawing on touch devices (close #2128)
Support adding nodes to ways with double-tap with on touch devices (close #2677)
Support double-tap-to-zoom with styluses on touch devices
Don't accept double click/tap events if the taps are far apart
Don't re-enter modeSelect when clicking the selected feature again
  • Loading branch information
quincylvania committed Mar 6, 2020
1 parent 83a51a4 commit f8f69a7
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 64 deletions.
4 changes: 2 additions & 2 deletions modules/behavior/add_way.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function behaviorAddWay(context) {
.on('finish', behavior.cancel);

context.map()
.dblclickEnable(false);
.dblclickZoomEnable(false);

surface.call(draw);
}
Expand All @@ -30,7 +30,7 @@ export function behaviorAddWay(context) {

behavior.cancel = function() {
window.setTimeout(function() {
context.map().dblclickEnable(true);
context.map().dblclickZoomEnable(true);
}, 1000);

context.enter(modeBrowse(context));
Expand Down
14 changes: 7 additions & 7 deletions modules/behavior/drag.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export function behaviorDrag() {
}


function dragstart() {
function pointerdown() {
_target = this;
_event = eventOf(_target, arguments);

Expand All @@ -75,8 +75,8 @@ export function behaviorDrag() {
var selectEnable = d3_event_userSelectSuppress();

d3_select(window)
.on(_pointerPrefix + 'move.drag', dragmove)
.on(_pointerPrefix + 'up.drag', dragend, true);
.on(_pointerPrefix + 'move.drag', pointermove)
.on(_pointerPrefix + 'up.drag', pointerup, true);

if (_origin) {
offset = _origin.apply(_target, arguments);
Expand All @@ -94,7 +94,7 @@ export function behaviorDrag() {
}


function dragmove() {
function pointermove() {
var p = point();
var dx = p[0] - startOrigin[0];
var dy = p[1] - startOrigin[1];
Expand All @@ -118,7 +118,7 @@ export function behaviorDrag() {
}


function dragend() {
function pointerup() {
if (started) {
_event({ type: 'end' });

Expand Down Expand Up @@ -147,7 +147,7 @@ export function behaviorDrag() {

function behavior(selection) {
var matchesSelector = utilPrefixDOMProperty('matchesSelector');
var delegate = dragstart;
var delegate = pointerdown;

if (_selector) {
delegate = function() {
Expand All @@ -160,7 +160,7 @@ export function behaviorDrag() {
: datum && datum.properties && datum.properties.entity;

if (entity && target[matchesSelector](_selector)) {
return dragstart.call(target, entity);
return pointerdown.call(target, entity);
}
}
};
Expand Down
4 changes: 2 additions & 2 deletions modules/behavior/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ export function behaviorDraw(context) {
d3_event.stopPropagation();
}, true);

context.map().dblclickEnable(false);
context.map().dblclickZoomEnable(false);

window.setTimeout(function() {
context.map().dblclickEnable(true);
context.map().dblclickZoomEnable(true);
d3_select(window).on('click.draw-block', null);
}, 500);

Expand Down
6 changes: 3 additions & 3 deletions modules/behavior/draw_way.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph, baselin
.on('keyup.drawWay', keyup);

context.map()
.dblclickEnable(false)
.dblclickZoomEnable(false)
.on('drawn.draw', setActiveElements);

setActiveElements();
Expand Down Expand Up @@ -347,7 +347,7 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph, baselin
context.resumeChangeDispatch();

window.setTimeout(function() {
context.map().dblclickEnable(true);
context.map().dblclickZoomEnable(true);
}, 1000);

var isNewFeature = !mode.isContinuing;
Expand All @@ -365,7 +365,7 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph, baselin
context.resumeChangeDispatch();

window.setTimeout(function() {
context.map().dblclickEnable(true);
context.map().dblclickZoomEnable(true);
}, 1000);

context.surface()
Expand Down
7 changes: 5 additions & 2 deletions modules/behavior/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { modeSelectData } from '../modes/select_data';
import { modeSelectNote } from '../modes/select_note';
import { modeSelectError } from '../modes/select_error';
import { osmEntity, osmNote, QAItem } from '../osm';
import { utilArrayIdentical } from '../util/array';


export function behaviorSelect(context) {
Expand Down Expand Up @@ -136,8 +137,10 @@ export function behaviorSelect(context) {
// multiple things already selected, just show the menu...
mode.suppressMenu(false).reselect();
} else {
// select a single thing..
context.enter(modeSelect(context, [datum.id]).suppressMenu(_suppressMenu));
if (mode.id !== 'select' || !utilArrayIdentical(mode.selectedIDs(), [datum.id])) {
// select a single thing if it's not already selected
context.enter(modeSelect(context, [datum.id]).suppressMenu(_suppressMenu));
}
}

} else {
Expand Down
17 changes: 4 additions & 13 deletions modules/modes/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ export function modeSelect(context, selectedIDs) {
breatheBehavior.restartIfNeeded(context.surface());
});

context.surface()
.on('dblclick.select', dblclick);
context.map().doubleUpHandler()
.on('doubleUp.modeSelect', didDoubleUp);


selectElements();
Expand Down Expand Up @@ -359,7 +359,7 @@ export function modeSelect(context, selectedIDs) {
}


function dblclick() {
function didDoubleUp(loc) {
if (!context.map().withinEditableZoom()) return;

var target = d3_select(d3_event.target);
Expand All @@ -369,7 +369,7 @@ export function modeSelect(context, selectedIDs) {
if (!entity) return;

if (entity instanceof osmWay && target.classed('target')) {
var choice = geoChooseEdge(context.childNodes(entity), context.mouse(), context.projection);
var choice = geoChooseEdge(context.childNodes(entity), loc, context.projection);
var prev = entity.nodes[choice.index - 1];
var next = entity.nodes[choice.index];

Expand All @@ -378,16 +378,10 @@ export function modeSelect(context, selectedIDs) {
t('operations.add.annotation.vertex')
);

d3_event.preventDefault();
d3_event.stopPropagation();

} else if (entity.type === 'midpoint') {
context.perform(
actionAddMidpoint({ loc: entity.loc, edge: entity.edge }, osmNode()),
t('operations.add.annotation.vertex'));

d3_event.preventDefault();
d3_event.stopPropagation();
}
}

Expand Down Expand Up @@ -574,9 +568,6 @@ export function modeSelect(context, selectedIDs) {

var surface = context.surface();

surface
.on('dblclick.select', null);

surface
.selectAll('.selected-member')
.classed('selected-member', false);
Expand Down
49 changes: 37 additions & 12 deletions modules/renderer/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { utilDetect } from '../util/detect';
import { utilGetDimensions } from '../util/dimensions';
import { utilRebind } from '../util/rebind';
import { utilZoomPan } from '../util/zoom_pan';
import { utilDoubleUp } from '../util/double_up';

// constants
var TILESIZE = 256;
Expand Down Expand Up @@ -50,7 +51,7 @@ export function rendererMap(context) {
var surface = d3_select(null);

var _dimensions = [1, 1];
var _dblClickEnabled = true;
var _dblClickZoomEnabled = true;
var _redrawEnabled = true;
var _gestureTransformStart;
var _transformStart = projection.transform();
Expand Down Expand Up @@ -81,6 +82,7 @@ export function rendererMap(context) {
.on('end.map', function() {
_pointerDown = false;
});
var _doubleUpHandler = utilDoubleUp();

var scheduleRedraw = _throttle(redraw, 750);
// var isRedrawScheduled = false;
Expand Down Expand Up @@ -147,9 +149,9 @@ export function rendererMap(context) {
});

selection
.on('dblclick.map', dblClick)
.call(_zoomerPanner)
.call(_zoomerPanner.transform, projection.transform());
.call(_zoomerPanner.transform, projection.transform())
.on('dblclick.zoom', null); // override d3-zoom dblclick handling

supersurface = selection.append('div')
.attr('id', 'supersurface')
Expand All @@ -168,6 +170,7 @@ export function rendererMap(context) {

surface
.call(drawLabels.observe)
.call(_doubleUpHandler)
.on('gesturestart.surface', function() {
_gestureTransformStart = projection.transform();
})
Expand Down Expand Up @@ -203,6 +206,28 @@ export function rendererMap(context) {
// must call after surface init
updateAreaFill();

_doubleUpHandler.on('doubleUp.map', function(p0) {
if (!_dblClickZoomEnabled) return;

// don't zoom if targeting something other than the map itself
if (typeof d3_event.target.__data__ === 'object' &&
// or area fills
!d3_select(d3_event.target).classed('fill')) return;

var zoomOut = d3_event.shiftKey;

var t = projection.transform();

var p1 = t.invert(p0);

t = t.scale(zoomOut ? 0.5 : 2);

t.x = p0[0] - p1[0] * t.k;
t.y = p0[1] - p1[1] * t.k;

map.transformEase(t);
});

context.on('enter.map', function() {
if (map.editableDataEnabled(true /* skip zoom check */) && !_isTransformed) {
// redraw immediately any objects affected by a change in selectedIDs.
Expand Down Expand Up @@ -367,12 +392,7 @@ export function rendererMap(context) {
}


function dblClick() {
if (!_dblClickEnabled) {
d3_event.preventDefault();
d3_event.stopImmediatePropagation();
}
}



function gestureChange() {
Expand Down Expand Up @@ -662,9 +682,9 @@ export function rendererMap(context) {
};


map.dblclickEnable = function(val) {
if (!arguments.length) return _dblClickEnabled;
_dblClickEnabled = val;
map.dblclickZoomEnable = function(val) {
if (!arguments.length) return _dblClickZoomEnabled;
_dblClickZoomEnabled = val;
return map;
};

Expand Down Expand Up @@ -1051,5 +1071,10 @@ export function rendererMap(context) {
map.layers = drawLayers;


map.doubleUpHandler = function() {
return _doubleUpHandler;
};


return utilRebind(map, dispatch, 'on');
}
80 changes: 80 additions & 0 deletions modules/util/double_up.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { dispatch as d3_dispatch } from 'd3-dispatch';
import { mouse as d3_mouse } from 'd3-selection';

import { utilRebind } from './rebind';
import { geoVecLength } from '../geo/vector';

// a double-click / double-tap event detector with wider
export function utilDoubleUp() {

var dispatch = d3_dispatch('doubleUp');

var _maxTimespan = 500; // milliseconds
var _maxDistance = 20; // web pixels; be somewhat generous to account for touch devices
var _pointer; // object representing the pointer that could trigger double up

function pointerIsValidFor(loc) {
// second pointerup must occur within a small timeframe after the first pointerdown
return new Date().getTime() - _pointer.startTime <= _maxTimespan &&
// all pointer events must occur within a small distance of the first pointerdown
geoVecLength(_pointer.startLoc, loc) <= _maxDistance;
}

function pointerdown() {
// d3_mouse works since pointer events inherit from mouse events
var loc = d3_mouse(this);

if (_pointer && !pointerIsValidFor(loc)) {
// if this pointer is no longer valid, clear it so another can be started
_pointer = undefined;
}
if (!_pointer) {
// don't rely on the pointerId since it can change between down events on touch devices
_pointer = {
startLoc: loc,
startTime: new Date().getTime(),
upCount: 0
};
}
}

function pointerup() {
if (!_pointer) return;

_pointer.upCount += 1;

if (_pointer.upCount === 2) { // double up!
var loc = d3_mouse(this);
if (pointerIsValidFor(loc)) {
dispatch.call('doubleUp', this, loc);
}
// clear the pointer info in any case
_pointer = undefined;
}
}

function doubleUp(selection) {
if ('PointerEvent' in window) {
// dblclick isn't well supported on touch devices so manually use
// pointer events if they're available
selection
.on('pointerdown.doubleUp', pointerdown)
.on('pointerup.doubleUp', pointerup);
} else {
// fallback to dblclick
selection
.on('dblclick.doubleUp', function() {
dispatch.call('doubleUp', this, d3_mouse(this));
});
}
}

doubleUp.off = function(selection) {
selection
.on('pointerdown.doubleUp', null)
.on('pointerup.doubleUp', null)
.on('dblclick.doubleUp', null);
};

return utilRebind(doubleUp, dispatch, 'on');
}
Loading

0 comments on commit f8f69a7

Please sign in to comment.