Extract viewport nudging code from several places to geoViewportEdge

This commit is contained in:
Bryan Housel
2017-12-18 09:50:17 -05:00
parent bcd511573f
commit 18c97d52c8
7 changed files with 150 additions and 135 deletions

View File

@@ -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());
}

View File

@@ -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;
}
}

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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]);
});
});
});