mirror of
https://github.com/FoggedLens/iD.git
synced 2026-05-15 05:30:35 +02:00
Replace inconsistently-supported dblclick events with custom handler 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
This commit is contained in:
@@ -17,7 +17,7 @@ export function behaviorAddWay(context) {
|
||||
.on('finish', behavior.cancel);
|
||||
|
||||
context.map()
|
||||
.dblclickEnable(false);
|
||||
.dblclickZoomEnable(false);
|
||||
|
||||
surface.call(draw);
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -64,7 +64,7 @@ export function behaviorDrag() {
|
||||
}
|
||||
|
||||
|
||||
function dragstart() {
|
||||
function pointerdown() {
|
||||
_target = this;
|
||||
_event = eventOf(_target, arguments);
|
||||
|
||||
@@ -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);
|
||||
@@ -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];
|
||||
@@ -118,7 +118,7 @@ export function behaviorDrag() {
|
||||
}
|
||||
|
||||
|
||||
function dragend() {
|
||||
function pointerup() {
|
||||
if (started) {
|
||||
_event({ type: 'end' });
|
||||
|
||||
@@ -147,7 +147,7 @@ export function behaviorDrag() {
|
||||
|
||||
function behavior(selection) {
|
||||
var matchesSelector = utilPrefixDOMProperty('matchesSelector');
|
||||
var delegate = dragstart;
|
||||
var delegate = pointerdown;
|
||||
|
||||
if (_selector) {
|
||||
delegate = function() {
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
@@ -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 {
|
||||
|
||||
+4
-13
@@ -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();
|
||||
@@ -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);
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+37
-12
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
@@ -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')
|
||||
@@ -168,6 +170,7 @@ export function rendererMap(context) {
|
||||
|
||||
surface
|
||||
.call(drawLabels.observe)
|
||||
.call(_doubleUpHandler)
|
||||
.on('gesturestart.surface', function() {
|
||||
_gestureTransformStart = projection.transform();
|
||||
})
|
||||
@@ -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.
|
||||
@@ -367,12 +392,7 @@ export function rendererMap(context) {
|
||||
}
|
||||
|
||||
|
||||
function dblClick() {
|
||||
if (!_dblClickEnabled) {
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function gestureChange() {
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -1051,5 +1071,10 @@ export function rendererMap(context) {
|
||||
map.layers = drawLayers;
|
||||
|
||||
|
||||
map.doubleUpHandler = function() {
|
||||
return _doubleUpHandler;
|
||||
};
|
||||
|
||||
|
||||
return utilRebind(map, dispatch, 'on');
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { dispatch as d3_dispatch } from 'd3-dispatch';
|
||||
import { interpolateZoom } from 'd3-interpolate';
|
||||
import { event as d3_event, customEvent as d3_customEvent, select as d3_select, mouse as d3_mouse } from 'd3-selection';
|
||||
import { event as d3_event, customEvent as d3_customEvent, mouse as d3_mouse } from 'd3-selection';
|
||||
import { interrupt as d3_interrupt } from 'd3-transition';
|
||||
import constant from '../../node_modules/d3-zoom/src/constant.js';
|
||||
import ZoomEvent from '../../node_modules/d3-zoom/src/event.js';
|
||||
@@ -70,7 +70,6 @@ export function utilZoomPan() {
|
||||
function zoom(selection) {
|
||||
selection
|
||||
.property('__zoom', defaultTransform)
|
||||
.on('dblclick.zoom', dblclicked)
|
||||
.on('pointerdown.zoom', pointerdown)
|
||||
.on('pointermove.zoom', pointermove)
|
||||
.on('pointerup.zoom pointercancel.zoom', pointerup)
|
||||
@@ -247,20 +246,6 @@ export function utilZoomPan() {
|
||||
}
|
||||
}
|
||||
|
||||
function dblclicked() {
|
||||
if (!filter.apply(this, arguments)) return;
|
||||
var t0 = this.__zoom,
|
||||
p0 = d3_mouse(this),
|
||||
p1 = t0.invert(p0),
|
||||
k1 = t0.k * (d3_event.shiftKey ? 0.5 : 2),
|
||||
t1 = constrain(translate(scale(t0, k1), p0, p1), extent.apply(this, arguments), translateExtent);
|
||||
|
||||
d3_event.preventDefault();
|
||||
d3_event.stopImmediatePropagation();
|
||||
if (duration > 0) d3_select(this).transition().duration(duration).call(schedule, t1, p0);
|
||||
else d3_select(this).call(zoom.transform, t1);
|
||||
}
|
||||
|
||||
var downPointerIDs = new Set();
|
||||
|
||||
function pointerdown() {
|
||||
@@ -347,13 +332,6 @@ export function utilZoomPan() {
|
||||
if (g.touch0) g.touch0[1] = this.__zoom.invert(g.touch0[0]);
|
||||
else {
|
||||
g.end();
|
||||
// If this was a dbltap, reroute to the (optional) dblclick.zoom handler.
|
||||
if (g.taps === 2) {
|
||||
// This currently never appears to be called but mobile Safari still
|
||||
// seems to get regular dblclick events upon double-tapping.
|
||||
var p = d3_select(this).on('dblclick.zoom');
|
||||
if (p) p.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user