From 18c97d52c80fe9ebfe46b793556b94e3726b3016 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Mon, 18 Dec 2017 09:50:17 -0500 Subject: [PATCH] Extract viewport nudging code from several places to geoViewportEdge --- modules/behavior/draw.js | 36 +++++++-------- modules/geo/geo.js | 24 ++++++++++ modules/geo/index.js | 1 + modules/modes/drag_node.js | 25 +---------- modules/modes/move.js | 92 +++++++++++++++----------------------- modules/modes/rotate.js | 75 ++++++++++++++++--------------- test/spec/geo/geo.js | 32 +++++++++++++ 7 files changed, 150 insertions(+), 135 deletions(-) diff --git a/modules/behavior/draw.js b/modules/behavior/draw.js index 6a6ed2b2b..61ca00af5 100644 --- a/modules/behavior/draw.js +++ b/modules/behavior/draw.js @@ -14,7 +14,8 @@ import { behaviorTail } from './tail'; import { geoChooseEdge, - geoEuclideanDistance + geoEuclideanDistance, + geoViewportEdge } from '../geo'; import { utilRebind } from '../util/rebind'; @@ -115,31 +116,28 @@ export function behaviorDraw(context) { function click() { - var d = datum(); + var trySnap = geoViewportEdge(context.mouse, context.map().dimensions()) !== null; - // Try to snap.. - // See also: `modes/drag_node.js doMove()` - if (d.type === 'way') { - var dims = context.map().dimensions(); - var mouse = context.mouse(); - var pad = 5; - var trySnap = mouse[0] > pad && mouse[0] < dims[0] - pad && - mouse[1] > pad && mouse[1] < dims[1] - pad; + if (trySnap) { + // If we're not at the edge of the viewport, try to snap.. + // See also: `modes/drag_node.js doMove()` + var d = datum(); - if (trySnap) { + // Snap to a node + if (d.type === 'node') { + dispatch.call('clickNode', this, d); + return; + + // Snap to a way (not an area fill) + } else if (d.type === 'way' && !d3_select(d3_event.sourceEvent.target).classed('fill')) { var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection); var edge = [d.nodes[choice.index - 1], d.nodes[choice.index]]; dispatch.call('clickWay', this, choice.loc, edge); - } else { - dispatch.call('click', this, context.map().mouseCoordinates()); + return; } - - } else if (d.type === 'node') { - dispatch.call('clickNode', this, d); - - } else { - dispatch.call('click', this, context.map().mouseCoordinates()); } + + dispatch.call('click', this, context.map().mouseCoordinates()); } diff --git a/modules/geo/geo.js b/modules/geo/geo.js index 3d083d219..2f74eb40a 100644 --- a/modules/geo/geo.js +++ b/modules/geo/geo.js @@ -275,3 +275,27 @@ export function geoPathLength(path) { } return length; } + + +// If the given point is at the edge of the padded viewport, +// return a vector that will nudge the viewport in that direction +export function geoViewportEdge(point, dimensions) { + var pad = [80, 20, 50, 20]; // top, right, bottom, left + var x = 0; + var y = 0; + + if (point[0] > dimensions[0] - pad[1]) + x = -10; + if (point[0] < pad[3]) + x = 10; + if (point[1] > dimensions[1] - pad[2]) + y = -10; + if (point[1] < pad[0]) + y = 10; + + if (x || y) { + return [x, y]; + } else { + return null; + } +} diff --git a/modules/geo/index.js b/modules/geo/index.js index 5e37b16fe..8966c218b 100644 --- a/modules/geo/index.js +++ b/modules/geo/index.js @@ -21,3 +21,4 @@ export { geoPointInPolygon } from './geo.js'; export { geoPolygonContainsPolygon } from './geo.js'; export { geoPolygonIntersectsPolygon } from './geo.js'; export { geoSphericalDistance } from './geo.js'; +export { geoViewportEdge } from './geo.js'; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index 90298a485..b52c2da97 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -23,7 +23,7 @@ import { modeSelect } from './index'; -import { geoChooseEdge } from '../geo'; +import { geoChooseEdge, geoViewportEdge } from '../geo'; import { osmNode } from '../osm'; import { utilEntitySelector } from '../util'; import { uiFlash } from '../ui'; @@ -50,27 +50,6 @@ export function modeDragNode(context) { return [a[0] - b[0], a[1] - b[1]]; } - function edge(point, size) { - var pad = [80, 20, 50, 20]; // top, right, bottom, left - var x = 0; - var y = 0; - - if (point[0] > size[0] - pad[1]) - x = -10; - if (point[0] < pad[3]) - x = 10; - if (point[1] > size[1] - pad[2]) - y = -10; - if (point[1] < pad[0]) - y = 10; - - if (x || y) { - return [x, y]; - } else { - return null; - } - } - function startNudge(entity, nudge) { if (_nudgeInterval) window.clearInterval(_nudgeInterval); @@ -197,7 +176,7 @@ export function modeDragNode(context) { _lastLoc = context.projection.invert(d3_event.point); doMove(entity); - var nudge = edge(d3_event.point, context.map().dimensions()); + var nudge = geoViewportEdge(d3_event.point, context.map().dimensions()); if (nudge) { startNudge(entity, nudge); } else { diff --git a/modules/modes/move.js b/modules/modes/move.js index b9ce5b7b3..e91439b1b 100644 --- a/modules/modes/move.js +++ b/modules/modes/move.js @@ -8,6 +8,7 @@ import { t } from '../util/locale'; import { actionMove } from '../actions'; import { behaviorEdit } from '../behavior'; +import { geoViewportEdge } from '../geo'; import { modeBrowse, @@ -30,23 +31,24 @@ export function modeMove(context, entityIDs, baseGraph) { button: 'browse' }; - var keybinding = d3_keybinding('move'), - behaviors = [ - behaviorEdit(context), - operationCircularize(entityIDs, context).behavior, - operationDelete(entityIDs, context).behavior, - operationOrthogonalize(entityIDs, context).behavior, - operationReflectLong(entityIDs, context).behavior, - operationReflectShort(entityIDs, context).behavior, - operationRotate(entityIDs, context).behavior - ], - annotation = entityIDs.length === 1 ? - t('operations.move.annotation.' + context.geometry(entityIDs[0])) : - t('operations.move.annotation.multiple'), - prevGraph, - cache, - origin, - nudgeInterval; + var keybinding = d3_keybinding('move'); + var behaviors = [ + behaviorEdit(context), + operationCircularize(entityIDs, context).behavior, + operationDelete(entityIDs, context).behavior, + operationOrthogonalize(entityIDs, context).behavior, + operationReflectLong(entityIDs, context).behavior, + operationReflectShort(entityIDs, context).behavior, + operationRotate(entityIDs, context).behavior + ]; + var annotation = entityIDs.length === 1 ? + t('operations.move.annotation.' + context.geometry(entityIDs[0])) : + t('operations.move.annotation.multiple'); + + var _prevGraph; + var _cache; + var _origin; + var _nudgeInterval; function vecSub(a, b) { @@ -54,52 +56,30 @@ export function modeMove(context, entityIDs, baseGraph) { } - function edge(point, size) { - var pad = [80, 20, 50, 20], // top, right, bottom, left - x = 0, - y = 0; - - if (point[0] > size[0] - pad[1]) - x = -10; - if (point[0] < pad[3]) - x = 10; - if (point[1] > size[1] - pad[2]) - y = -10; - if (point[1] < pad[0]) - y = 10; - - if (x || y) { - return [x, y]; - } else { - return null; - } - } - - function doMove(nudge) { nudge = nudge || [0, 0]; var fn; - if (prevGraph !== context.graph()) { - cache = {}; - origin = context.map().mouseCoordinates(); + if (_prevGraph !== context.graph()) { + _cache = {}; + _origin = context.map().mouseCoordinates(); fn = context.perform; } else { fn = context.overwrite; } - var currMouse = context.mouse(), - origMouse = context.projection(origin), - delta = vecSub(vecSub(currMouse, origMouse), nudge); + var currMouse = context.mouse(); + var origMouse = context.projection(_origin); + var delta = vecSub(vecSub(currMouse, origMouse), nudge); - fn(actionMove(entityIDs, delta, context.projection, cache), annotation); - prevGraph = context.graph(); + fn(actionMove(entityIDs, delta, context.projection, _cache), annotation); + _prevGraph = context.graph(); } function startNudge(nudge) { - if (nudgeInterval) window.clearInterval(nudgeInterval); - nudgeInterval = window.setInterval(function() { + if (_nudgeInterval) window.clearInterval(_nudgeInterval); + _nudgeInterval = window.setInterval(function() { context.pan(nudge); doMove(nudge); }, 50); @@ -107,16 +87,16 @@ export function modeMove(context, entityIDs, baseGraph) { function stopNudge() { - if (nudgeInterval) { - window.clearInterval(nudgeInterval); - nudgeInterval = null; + if (_nudgeInterval) { + window.clearInterval(_nudgeInterval); + _nudgeInterval = null; } } function move() { doMove(); - var nudge = edge(context.mouse(), context.map().dimensions()); + var nudge = geoViewportEdge(context.mouse(), context.map().dimensions()); if (nudge) { startNudge(nudge); } else { @@ -150,9 +130,9 @@ export function modeMove(context, entityIDs, baseGraph) { mode.enter = function() { - origin = context.map().mouseCoordinates(); - prevGraph = null; - cache = {}; + _origin = context.map().mouseCoordinates(); + _prevGraph = null; + _cache = {}; behaviors.forEach(function(behavior) { context.install(behavior); diff --git a/modules/modes/rotate.js b/modules/modes/rotate.js index 6448d5333..d889a174b 100644 --- a/modules/modes/rotate.js +++ b/modules/modes/rotate.js @@ -38,66 +38,67 @@ export function modeRotate(context, entityIDs) { button: 'browse' }; - var keybinding = d3_keybinding('rotate'), - behaviors = [ - behaviorEdit(context), - operationCircularize(entityIDs, context).behavior, - operationDelete(entityIDs, context).behavior, - operationMove(entityIDs, context).behavior, - operationOrthogonalize(entityIDs, context).behavior, - operationReflectLong(entityIDs, context).behavior, - operationReflectShort(entityIDs, context).behavior - ], - annotation = entityIDs.length === 1 ? - t('operations.rotate.annotation.' + context.geometry(entityIDs[0])) : - t('operations.rotate.annotation.multiple'), - prevGraph, - prevAngle, - prevTransform, - pivot; + var keybinding = d3_keybinding('rotate'); + var behaviors = [ + behaviorEdit(context), + operationCircularize(entityIDs, context).behavior, + operationDelete(entityIDs, context).behavior, + operationMove(entityIDs, context).behavior, + operationOrthogonalize(entityIDs, context).behavior, + operationReflectLong(entityIDs, context).behavior, + operationReflectShort(entityIDs, context).behavior + ]; + var annotation = entityIDs.length === 1 ? + t('operations.rotate.annotation.' + context.geometry(entityIDs[0])) : + t('operations.rotate.annotation.multiple'); + + var _prevGraph; + var _prevAngle; + var _prevTransform; + var _pivot; function doRotate() { var fn; - if (context.graph() !== prevGraph) { + if (context.graph() !== _prevGraph) { fn = context.perform; } else { fn = context.replace; } - // projection changed, recalculate pivot + // projection changed, recalculate _pivot var projection = context.projection; var currTransform = projection.transform(); - if (!prevTransform || - currTransform.k !== prevTransform.k || - currTransform.x !== prevTransform.x || - currTransform.y !== prevTransform.y) { + if (!_prevTransform || + currTransform.k !== _prevTransform.k || + currTransform.x !== _prevTransform.x || + currTransform.y !== _prevTransform.y) { - var nodes = utilGetAllNodes(entityIDs, context.graph()), - points = nodes.map(function(n) { return projection(n.loc); }); + var nodes = utilGetAllNodes(entityIDs, context.graph()); + var points = nodes.map(function(n) { return projection(n.loc); }); if (points.length === 1) { // degenerate case - pivot = points[0]; + _pivot = points[0]; } else if (points.length === 2) { - pivot = geoInterp(points[0], points[1], 0.5); + _pivot = geoInterp(points[0], points[1], 0.5); } else { - pivot = d3_polygonCentroid(d3_polygonHull(points)); + _pivot = d3_polygonCentroid(d3_polygonHull(points)); } - prevAngle = undefined; + _prevAngle = undefined; } - var currMouse = context.mouse(), - currAngle = Math.atan2(currMouse[1] - pivot[1], currMouse[0] - pivot[0]); + var currMouse = context.mouse(); + var currAngle = Math.atan2(currMouse[1] - _pivot[1], currMouse[0] - _pivot[0]); - if (typeof prevAngle === 'undefined') prevAngle = currAngle; - var delta = currAngle - prevAngle; + if (typeof _prevAngle === 'undefined') _prevAngle = currAngle; + var delta = currAngle - _prevAngle; - fn(actionRotate(entityIDs, pivot, delta, projection), annotation); + fn(actionRotate(entityIDs, _pivot, delta, projection), annotation); - prevTransform = currTransform; - prevAngle = currAngle; - prevGraph = context.graph(); + _prevTransform = currTransform; + _prevAngle = currAngle; + _prevGraph = context.graph(); } diff --git a/test/spec/geo/geo.js b/test/spec/geo/geo.js index 6b2e0cb85..b4ac874c6 100644 --- a/test/spec/geo/geo.js +++ b/test/spec/geo/geo.js @@ -411,4 +411,36 @@ describe('iD.geo', function() { expect(iD.geoPathLength(path)).to.eql(0); }); }); + + describe('geoViewportEdge', function() { + var dimensions = [1000, 1000]; + it('returns null if the point is not at the edge', function() { + expect(iD.geoViewportEdge([500, 500], dimensions)).to.be.null; + }); + it('nudges top edge', function() { + expect(iD.geoViewportEdge([500, 5], dimensions)).to.eql([0, 10]); + }); + it('nudges top-right corner', function() { + expect(iD.geoViewportEdge([995, 5], dimensions)).to.eql([-10, 10]); + }); + it('nudges right edge', function() { + expect(iD.geoViewportEdge([995, 500], dimensions)).to.eql([-10, 0]); + }); + it('nudges bottom-right corner', function() { + expect(iD.geoViewportEdge([995, 995], dimensions)).to.eql([-10, -10]); + }); + it('nudges bottom edge', function() { + expect(iD.geoViewportEdge([500, 995], dimensions)).to.eql([0, -10]); + }); + it('nudges bottom-left corner', function() { + expect(iD.geoViewportEdge([5, 995], dimensions)).to.eql([10, -10]); + }); + it('nudges left edge', function() { + expect(iD.geoViewportEdge([5, 500], dimensions)).to.eql([10, 0]); + }); + it('nudges top-left corner', function() { + expect(iD.geoViewportEdge([5, 5], dimensions)).to.eql([10, 10]); + }); + }); + });