diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 7810a76c6..e549c891b 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -22,15 +22,19 @@ export function behaviorDraw(context) { var keybinding = utilKeybinding('draw'); - var _hover = behaviorHover(context).altDisables(true).ignoreVertex(true) + var _hover = behaviorHover(context) + .altDisables(true) + .ignoreVertex(true) .on('hover', context.ui().sidebar.hover); - var edit = behaviorEdit(context); + var _edit = behaviorEdit(context); - var closeTolerance = 4; - var tolerance = 12; + var _closeTolerance = 4; + var _tolerance = 12; var _mouseLeave = false; var _lastMouse = null; + var _downPointerId; + // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; @@ -58,6 +62,9 @@ export function behaviorDraw(context) { function pointerdown() { + if (_downPointerId) return; + var _downPointerId = d3_event.pointerId || 'mouse'; + var pointerLocGetter = utilFastMouse(this); var element = d3_select(this); @@ -67,14 +74,18 @@ export function behaviorDraw(context) { element.on(_pointerPrefix + 'move.draw', null); d3_select(window).on(_pointerPrefix + 'up.draw', function() { - var t2 = +new Date(); - var p2 = pointerLocGetter(d3_event); - var dist = geoVecLength(p1, p2); + + if (_downPointerId !== (d3_event.pointerId || 'mouse')) return; + _downPointerId = null; element.on(_pointerPrefix + 'move.draw', pointermove); d3_select(window).on(_pointerPrefix + 'up.draw', null); - if (dist < closeTolerance || (dist < tolerance && (t2 - t1) < 500)) { + var t2 = +new Date(); + var p2 = pointerLocGetter(d3_event); + var dist = geoVecLength(p1, p2); + + if (dist < _closeTolerance || (dist < _tolerance && (t2 - t1) < 500)) { // Prevent a quick second click d3_select(window).on('click.draw-block', function() { d3_event.stopPropagation(); @@ -94,6 +105,10 @@ export function behaviorDraw(context) { function pointermove() { + if ((d3_event.pointerType && d3_event.pointerType !== 'mouse') || + d3_event.buttons || + _downPointerId) return; + _lastMouse = d3_event; dispatch.call('move', this, datum()); } @@ -150,7 +165,7 @@ export function behaviorDraw(context) { var currSpace = context.map().mouse(); if (_disableSpace && _lastSpace) { var dist = geoVecLength(_lastSpace, currSpace); - if (dist > tolerance) { + if (dist > _tolerance) { _disableSpace = false; } } @@ -196,7 +211,9 @@ export function behaviorDraw(context) { function behavior(selection) { context.install(_hover); - context.install(edit); + context.install(_edit); + + _downPointerId = null; keybinding .on('⌫', backspace) @@ -222,7 +239,7 @@ export function behaviorDraw(context) { behavior.off = function(selection) { context.ui().sidebar.hover.cancel(); context.uninstall(_hover); - context.uninstall(edit); + context.uninstall(_edit); selection .on('mouseenter.draw', null) diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index ad0687106..ceff6fd24 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -18,25 +18,26 @@ import { osmNode } from '../osm/node'; import { utilRebind } from '../util/rebind'; import { utilKeybinding } from '../util'; -export function behaviorDrawWay(context, wayID, index, mode, startGraph) { +export function behaviorDrawWay(context, wayID, mode, startGraph) { var dispatch = d3_dispatch('rejectedSelfIntersection'); - var _origWay = context.entity(wayID); - - var _annotation = t((_origWay.isDegenerate() ? - 'operations.start.annotation.' : - 'operations.continue.annotation.') + _origWay.geometry(context.graph()) - ); - var behavior = behaviorDraw(context); - behavior.hover().initialNodeID(index ? _origWay.nodes[index] : - (_origWay.isClosed() ? _origWay.nodes[_origWay.nodes.length - 2] : _origWay.nodes[_origWay.nodes.length - 1])); + + // Must be set by `drawWay.nodeIndex` before each install of this behavior. + var _nodeIndex; + + var _origWay; + var _wayGeometry; + var _headNodeID; + var _annotation; // The osmNode to be placed. // This is temporary and just follows the mouse cursor until an "add" event occurs. var _drawNode; + var _didResolveTempEdit = false; + function createDrawNode(loc) { // don't make the draw node until we actually need it _drawNode = osmNode({ loc: loc }); @@ -47,7 +48,7 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { var way = graph.entity(wayID); return graph .replace(_drawNode) - .replace(way.addNode(_drawNode.id, index)); + .replace(way.addNode(_drawNode.id, _nodeIndex)); }, _annotation); context.resumeChangeDispatch(); @@ -70,14 +71,6 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { context.resumeChangeDispatch(); } - var _didResolveTempEdit = false; - - // Push an annotated state for undo to return back to. - // We must make sure to replace or remove it later. - context.pauseChangeDispatch(); - context.perform(actionNoop(), _annotation); - context.resumeChangeDispatch(); - function keydown() { if (d3_event.keyCode === utilKeybinding.modifierCodes.alt) { @@ -241,6 +234,26 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { var drawWay = function(surface) { + _drawNode = undefined; + _didResolveTempEdit = false; + _origWay = context.entity(wayID); + _headNodeID = typeof _nodeIndex === 'number' ? _origWay.nodes[_nodeIndex] : + (_origWay.isClosed() ? _origWay.nodes[_origWay.nodes.length - 2] : _origWay.nodes[_origWay.nodes.length - 1]); + _wayGeometry = _origWay.geometry(context.graph()); + _annotation = t((_origWay.isDegenerate() ? + 'operations.start.annotation.' : + 'operations.continue.annotation.') + _wayGeometry + ); + + // Push an annotated state for undo to return back to. + // We must make sure to replace or remove it later. + context.pauseChangeDispatch(); + context.perform(actionNoop(), _annotation); + context.resumeChangeDispatch(); + + behavior.hover() + .initialNodeID(_headNodeID); + behavior .on('move', move) .on('click', drawWay.add) @@ -279,6 +292,9 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { context.resumeChangeDispatch(); } + _drawNode = undefined; + _nodeIndex = undefined; + context.map() .on('drawn.draw', null); @@ -301,6 +317,7 @@ export function behaviorDrawWay(context, wayID, index, 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 @@ -353,6 +370,15 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { // Connect the way to an existing node drawWay.addNode = function(node, d) { + + // finish drawing if the mapper targets the prior node + if (node.id === _headNodeID || + // or the first node when drawing an area + (_wayGeometry === 'area' && node.id === _origWay.first())) { + drawWay.finish(); + return; + } + attemptAdd(d, node.loc, function() { context.replace( function actionReplaceDrawNode(graph) { @@ -363,7 +389,7 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { .replace(graph.entity(wayID).removeNode(_drawNode.id)) .remove(_drawNode); return graph - .replace(graph.entity(wayID).addNode(node.id, index)); + .replace(graph.entity(wayID).addNode(node.id, _nodeIndex)); }, _annotation ); @@ -421,6 +447,13 @@ export function behaviorDrawWay(context, wayID, index, mode, startGraph) { }; + drawWay.nodeIndex = function(val) { + if (!arguments.length) return _nodeIndex; + _nodeIndex = val; + return drawWay; + }; + + drawWay.activeID = function() { if (!arguments.length) return _drawNode && _drawNode.id; // no assign diff --git a/modules/modes/draw_area.js b/modules/modes/draw_area.js index 7c050aba5..902d4e870 100644 --- a/modules/modes/draw_area.js +++ b/modules/modes/draw_area.js @@ -8,50 +8,29 @@ export function modeDrawArea(context, wayID, startGraph, button) { id: 'draw-area' }; - var behavior; + var behavior = behaviorDrawWay(context, wayID, mode, startGraph) + .on('rejectedSelfIntersection.modeDrawArea', function() { + context.ui().flash + .text(t('self_intersection.error.areas'))(); + }); mode.wayID = wayID; mode.enter = function() { - var way = context.entity(wayID); - - behavior = behaviorDrawWay(context, wayID, undefined, mode, startGraph) - .on('rejectedSelfIntersection.modeDrawArea', function() { - context.ui().flash - .text(t('self_intersection.error.areas'))(); - }); - - var addNode = behavior.addNode; - - behavior.addNode = function(node, d) { - var length = way.nodes.length; - var penultimate = length > 2 ? way.nodes[length - 2] : null; - - if (node.id === way.first() || node.id === penultimate) { - behavior.finish(); - } else { - addNode(node, d); - } - }; - context.install(behavior); }; - mode.exit = function() { context.uninstall(behavior); }; - mode.selectedIDs = function() { return [wayID]; }; - mode.activeID = function() { return (behavior && behavior.activeID()) || []; }; - return mode; } diff --git a/modules/modes/draw_line.js b/modules/modes/draw_line.js index 63dbc504d..9d7b0edfb 100644 --- a/modules/modes/draw_line.js +++ b/modules/modes/draw_line.js @@ -8,46 +8,31 @@ export function modeDrawLine(context, wayID, startGraph, button, affix, continui id: 'draw-line' }; - var behavior; + var behavior = behaviorDrawWay(context, wayID, mode, startGraph) + .on('rejectedSelfIntersection.modeDrawLine', function() { + context.ui().flash + .text(t('self_intersection.error.lines'))(); + }); mode.wayID = wayID; mode.isContinuing = continuing; mode.enter = function() { - var way = context.entity(wayID); - var index = (affix === 'prefix') ? 0 : undefined; - var headID = (affix === 'prefix') ? way.first() : way.last(); - - behavior = behaviorDrawWay(context, wayID, index, mode, startGraph) - .on('rejectedSelfIntersection.modeDrawLine', function() { - context.ui().flash - .text(t('self_intersection.error.lines'))(); - }); - - var addNode = behavior.addNode; - behavior.addNode = function(node, d) { - if (node.id === headID) { - behavior.finish(); - } else { - addNode(node, d); - } - }; + behavior + .nodeIndex(affix === 'prefix' ? 0 : undefined); context.install(behavior); }; - mode.exit = function() { context.uninstall(behavior); }; - mode.selectedIDs = function() { return [wayID]; }; - mode.activeID = function() { return (behavior && behavior.activeID()) || []; };