From 1214b2d5dd7486d9828f0f50321354c4c88fa98b Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Thu, 28 May 2020 13:50:51 -0400 Subject: [PATCH] Preview the draw segment upon pointerdown on touchscreens unless it becomes invalid Block creation of invalid geometries due to connecting to invalid sibling segments or nodes on the drawn way on touchscreens --- modules/behavior/draw.js | 86 +++++++++++++++++++----------------- modules/behavior/draw_way.js | 18 ++++++-- 2 files changed, 60 insertions(+), 44 deletions(-) diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 83b25178a..b3a3d03d1 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -17,7 +17,7 @@ var _lastSpace = null; export function behaviorDraw(context) { var dispatch = d3_dispatch( - 'move', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish' + 'move', 'down', 'downcancel', 'click', 'clickWay', 'clickNode', 'undo', 'cancel', 'finish' ); var keybinding = utilKeybinding('draw'); @@ -32,8 +32,9 @@ export function behaviorDraw(context) { var _tolerance = 12; var _mouseLeave = false; var _lastMouse = null; + var _lastPointerUpEvent; - var _downPointerId; + var _downPointer; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; @@ -59,79 +60,81 @@ export function behaviorDraw(context) { return (d && d.properties && d.properties.target) ? d : {}; } - var _lastPointerUpEvent; - function pointerdown() { - if (_downPointerId) return; - var _downPointerId = d3_event.pointerId || 'mouse'; + if (_downPointer) return; var pointerLocGetter = utilFastMouse(this); + _downPointer = { + id: d3_event.pointerId || 'mouse', + pointerLocGetter: pointerLocGetter, + downTime: +new Date(), + downLoc: pointerLocGetter(d3_event) + }; - var element = d3_select(this); - var t1 = +new Date(); - var p1 = pointerLocGetter(d3_event); + dispatch.call('pointerdown', this, datum()); + } - element.on(_pointerPrefix + 'move.draw', null); + function pointerup() { - d3_select(window) - .on(_pointerPrefix + 'up.draw', pointerup, true); + if (!_downPointer || _downPointer.id !== (d3_event.pointerId || 'mouse')) return; - function pointerup() { + var downPointer = _downPointer; + _downPointer = null; - if (_downPointerId !== (d3_event.pointerId || 'mouse')) return; - _downPointerId = null; + _lastPointerUpEvent = d3_event; - _lastPointerUpEvent = d3_event; + if (downPointer.isCancelled) return; - element.on(_pointerPrefix + 'move.draw', pointermove); - d3_select(window).on(_pointerPrefix + 'up.draw', null); + var t2 = +new Date(); + var p2 = downPointer.pointerLocGetter(d3_event); + var dist = geoVecLength(downPointer.downLoc, p2); - var t2 = +new Date(); - var p2 = pointerLocGetter(d3_event); - var dist = geoVecLength(p1, p2); + if (dist < _closeTolerance || (dist < _tolerance && (t2 - downPointer.downTime) < 500)) { + // Prevent a quick second click + d3_select(window).on('click.draw-block', function() { + d3_event.stopPropagation(); + }, true); - if (dist < _closeTolerance || (dist < _tolerance && (t2 - t1) < 500)) { - // Prevent a quick second click - d3_select(window).on('click.draw-block', function() { - d3_event.stopPropagation(); - }, true); + context.map().dblclickZoomEnable(false); - context.map().dblclickZoomEnable(false); + window.setTimeout(function() { + context.map().dblclickZoomEnable(true); + d3_select(window).on('click.draw-block', null); + }, 500); - window.setTimeout(function() { - context.map().dblclickZoomEnable(true); - d3_select(window).on('click.draw-block', null); - }, 500); - - click(p2); - } + click(p2); } } - function pointermove() { + if (_downPointer && !_downPointer.isCancelled) { + var p2 = _downPointer.pointerLocGetter(d3_event); + var dist = geoVecLength(_downPointer.downLoc, p2); + if (dist >= _closeTolerance) { + _downPointer.isCancelled = true; + dispatch.call('downcancel', this); + } + } + if ((d3_event.pointerType && d3_event.pointerType !== 'mouse') || d3_event.buttons || - _downPointerId) return; + _downPointer) return; // HACK: Mobile Safari likes to send one or more `mouse` type pointermove // events immediately after non-mouse pointerup events; detect and ignore them. if (_lastPointerUpEvent && _lastPointerUpEvent.pointerType !== 'mouse' && - geoVecLength([_lastPointerUpEvent.clientX, _lastPointerUpEvent.clientY], [d3_event.clientX, d3_event.clientY]) < 2 && d3_event.timeStamp - _lastPointerUpEvent.timeStamp < 100) return; _lastMouse = d3_event; dispatch.call('move', this, datum()); } - function mouseenter() { _mouseLeave = false; } - function mouseleave() { _mouseLeave = true; } @@ -226,7 +229,7 @@ export function behaviorDraw(context) { context.install(_hover); context.install(_edit); - _downPointerId = null; + _downPointer = null; keybinding .on('⌫', backspace) @@ -242,6 +245,9 @@ export function behaviorDraw(context) { .on(_pointerPrefix + 'down.draw', pointerdown) .on(_pointerPrefix + 'move.draw', pointermove); + d3_select(window) + .on(_pointerPrefix + 'up.draw', pointerup, true); + d3_select(document) .call(keybinding); diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index 1c506cfb8..73d240e16 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -32,6 +32,8 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { var _headNodeID; var _annotation; + var _pointerHasMoved = false; + // The osmNode to be placed. // This is temporary and just follows the mouse cursor until an "add" event occurs. var _drawNode; @@ -244,6 +246,7 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { 'operations.start.annotation.' : 'operations.continue.annotation.') + _wayGeometry ); + _pointerHasMoved = false; // Push an annotated state for undo to return back to. // We must make sure to replace or remove it later. @@ -255,7 +258,16 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { .initialNodeID(_headNodeID); behavior - .on('move', move) + .on('move', function() { + _pointerHasMoved = true; + move.apply(this, arguments); + }) + .on('down', function() { + move.apply(this, arguments); + }) + .on('downcancel', function() { + if (_drawNode) removeDrawNode(); + }) .on('click', drawWay.add) .on('clickWay', drawWay.addWay) .on('clickNode', drawWay.addNode) @@ -318,7 +330,6 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { function attemptAdd(d, loc, doAdd) { - var didJustAddDrawNode = false; if (_drawNode) { // move the node to the final loc in case move wasn't called // consistently (e.g. on touch devices) @@ -326,12 +337,11 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { _drawNode = context.entity(_drawNode.id); } else { createDrawNode(loc); - didJustAddDrawNode = true; } checkGeometry(true /* includeDrawNode */); if ((d && d.properties && d.properties.nope) || context.surface().classed('nope')) { - if (didJustAddDrawNode) { + if (!_pointerHasMoved) { // prevent the temporary draw node from appearing on touch devices removeDrawNode(); }