Fix drag_node for touch targets and line snapping

This commit is contained in:
Bryan Housel
2017-12-21 20:31:20 -05:00
parent d82d5dc3d0
commit 5d9b051f84
8 changed files with 144 additions and 189 deletions
+2
View File
@@ -29,6 +29,8 @@
stroke-width: 12;
stroke-opacity: 0.8;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
}
/* `.target-nope` objects are explicitly forbidden to join to */
+22 -20
View File
@@ -33,8 +33,7 @@ export function behaviorDraw(context) {
var keybinding = d3_keybinding('draw');
var hover = behaviorHover(context)
.altDisables(true)
var hover = behaviorHover(context).altDisables(true)
.on('hover', context.ui().sidebar.hover);
var tail = behaviorTail();
var edit = behaviorEdit(context);
@@ -58,10 +57,8 @@ export function behaviorDraw(context) {
// When drawing, connect only to things classed as targets..
// (this excludes area fills and active drawing elements)
var selection = d3_select(element);
if (selection.classed('target')) return {};
var d = selection.datum();
return (d && d.id && context.hasEntity(d.id)) || {};
if (!selection.classed('target')) return {};
return selection.datum();
}
@@ -124,28 +121,33 @@ export function behaviorDraw(context) {
}
// related code
// - `mode/drag_node.js` `doMode()`
// - `behavior/draw.js` `click()`
// - `behavior/draw_way.js` `move()`
function click() {
var d = datum();
var target = d && d.id && context.hasEntity(d.id);
var trySnap = geoViewportEdge(context.mouse(), context.map().dimensions()) === null;
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();
// Snap to a node
if (d.type === 'node') {
dispatch.call('clickNode', this, d);
if (target && target.type === 'node') { // Snap to a node
dispatch.call('clickNode', this, target);
return;
// Snap to a way
} else if (d.type === 'way') {
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);
return;
} else if (target && target.type === 'way') { // Snap to a way
var choice = geoChooseEdge(
context.childNodes(target), context.mouse(), context.projection, context.activeID()
);
if (choice) {
var edge = [target.nodes[choice.index - 1], target.nodes[choice.index]];
dispatch.call('clickWay', this, choice.loc, edge);
return;
}
}
}
dispatch.call('click', this, context.map().mouseCoordinates());
dispatch.call('click', this, context.map().mouseCoordinates(), d);
}
+75 -77
View File
@@ -10,37 +10,22 @@ import {
} from '../actions';
import { behaviorDraw } from './draw';
import {
geoChooseEdge,
geoEdgeEqual
} from '../geo';
import {
modeBrowse,
modeSelect
} from '../modes';
import {
osmNode,
osmWay
} from '../osm';
import { utilEntitySelector } from '../util';
import { geoChooseEdge, geoEdgeEqual } from '../geo';
import { modeBrowse, modeSelect } from '../modes';
import { osmNode, osmWay } from '../osm';
export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
var origWay = context.entity(wayId);
var isArea = context.geometry(wayId) === 'area';
var tempEdits = 0;
var annotation = t((origWay.isDegenerate() ?
'operations.start.annotation.' :
'operations.continue.annotation.') + context.geometry(wayId));
var draw = behaviorDraw(context);
// var _activeIDs = [];
var _activeID;
var startIndex;
'operations.continue.annotation.') + context.geometry(wayId)
);
var behavior = behaviorDraw(context);
var _tempEdits = 0;
var _startIndex;
var start;
var end;
var segment;
@@ -48,9 +33,15 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// initialize the temporary drawing entities
if (!isArea) {
startIndex = typeof index === 'undefined' ? origWay.nodes.length - 1 : 0;
start = osmNode({ id: 'nStart', loc: context.entity(origWay.nodes[startIndex]).loc });
end = osmNode({ id: 'nEnd', loc: context.map().mouseCoordinates() });
_startIndex = (typeof index === 'undefined' ? origWay.nodes.length - 1 : 0);
start = osmNode({
id: 'nStart',
loc: context.entity(origWay.nodes[_startIndex]).loc
});
end = osmNode({
id: 'nEnd',
loc: context.map().mouseCoordinates()
});
segment = osmWay({
id: 'wTemp',
nodes: typeof index === 'undefined' ? [start.id, end.id] : [end.id, start.id],
@@ -63,21 +54,30 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Push an annotated state for undo to return back to.
// We must make sure to remove this edit later.
context.perform(actionNoop(), annotation);
tempEdits++;
_tempEdits++;
// Add the temporary drawing entities to the graph.
// We must make sure to remove this edit later.
context.perform(AddDrawEntities());
tempEdits++;
_tempEdits++;
// related code
// - `mode/drag_node.js` `doMode()`
// - `behavior/draw.js` `click()`
// - `behavior/draw_way.js` `move()`
function move(datum) {
var loc;
if (datum.type === 'node' && datum.id !== end.id) {
var target = datum && datum.id && context.hasEntity(datum.id);
if (target && target.type === 'node') { // snap to node
loc = datum.loc;
} else if (datum.type === 'way') {
loc = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection).loc;
} else if (target && target.type === 'way') { // snap to way
var choice = geoChooseEdge(
context.childNodes(target), context.mouse(), context.projection, end.id
);
if (choice) {
loc = choice.loc;
}
}
if (!loc) {
@@ -93,7 +93,7 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Undo popped the history back to the initial annotated no-op edit.
// Remove initial no-op edit and whatever edit happened immediately before it.
context.pop(2);
tempEdits = 0;
_tempEdits = 0;
if (context.hasEntity(wayId)) {
context.enter(mode);
@@ -104,17 +104,14 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
function setActiveElements() {
// _activeIDs = isArea ? [wayId, end.id] : [segment.id, start.id, end.id];
// context.surface().selectAll(utilEntitySelector(_activeIDs))
// .classed('active', true);
_activeID = end.id;
context.surface().selectAll('.' + end.id)
.classed('active', true);
}
var drawWay = function(surface) {
draw.on('move', move)
behavior
.on('move', move)
.on('click', drawWay.add)
.on('clickWay', drawWay.addWay)
.on('clickNode', drawWay.addNode)
@@ -128,7 +125,7 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
setActiveElements();
surface.call(draw);
surface.call(behavior);
context.history()
.on('undone.draw', undone);
@@ -139,8 +136,8 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Drawing was interrupted unexpectedly.
// This can happen if the user changes modes,
// clicks geolocate button, a hashchange event occurs, etc.
if (tempEdits) {
context.pop(tempEdits);
if (_tempEdits) {
context.pop(_tempEdits);
while (context.graph() !== startGraph) {
context.pop();
}
@@ -149,7 +146,7 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
context.map()
.on('drawn.draw', null);
surface.call(draw.off)
surface.call(behavior.off)
.selectAll('.active')
.classed('active', false);
@@ -201,12 +198,18 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Accept the current position of the temporary node and continue drawing.
drawWay.add = function(loc) {
drawWay.add = function(loc, datum) {
// shouldn't happen now?
// prevent duplicate nodes
var last = context.hasEntity(origWay.nodes[origWay.nodes.length - (isArea ? 2 : 1)]);
if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return;
// var last = context.hasEntity(origWay.nodes[origWay.nodes.length - (isArea ? 2 : 1)]);
// if (last && last.loc[0] === loc[0] && last.loc[1] === loc[1]) return;
context.pop(tempEdits);
if (datum && datum.id && /-nope/.test(datum.id)) { // can't click here
return;
}
context.pop(_tempEdits);
_tempEdits = 0;
if (isArea) {
context.perform(
@@ -222,31 +225,30 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
);
}
tempEdits = 0;
context.enter(mode);
};
// Connect the way to an existing way.
drawWay.addWay = function(loc, edge) {
if (isArea) {
context.pop(tempEdits);
context.pop(_tempEdits);
_tempEdits = 0;
if (isArea) {
context.perform(
AddDrawEntities(),
actionAddMidpoint({ loc: loc, edge: edge}, end),
annotation
);
} else {
var previousEdge = startIndex ?
[origWay.nodes[startIndex], origWay.nodes[startIndex - 1]] :
[origWay.nodes[0], origWay.nodes[1]];
// shouldn't happen now?
// var previousEdge = _startIndex ?
// [origWay.nodes[_startIndex], origWay.nodes[_startIndex - 1]] :
// [origWay.nodes[0], origWay.nodes[1]];
// Avoid creating duplicate segments
if (geoEdgeEqual(edge, previousEdge))
return;
context.pop(tempEdits);
// // Avoid creating duplicate segments
// if (geoEdgeEqual(edge, previousEdge))
// return;
var newNode = osmNode({ loc: loc });
context.perform(
@@ -256,7 +258,6 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
);
}
tempEdits = 0;
context.enter(mode);
};
@@ -264,23 +265,25 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Connect the way to an existing node and continue drawing.
drawWay.addNode = function(node) {
// Avoid creating duplicate segments
if (origWay.areAdjacent(node.id, origWay.nodes[origWay.nodes.length - 1])) return;
// shouldn't happen now?
// if (origWay.areAdjacent(node.id, origWay.nodes[origWay.nodes.length - 1])) return;
// Clicks should not occur on the drawing node, however a space keypress can
// sometimes grab that node's datum (before it gets classed as `active`?) #4016
if (node.id === end.id) {
drawWay.add(node.loc);
return;
}
// shouldn't happen now?
// if (node.id === end.id) {
// drawWay.add(node.loc);
// return;
// }
context.pop(tempEdits);
context.pop(_tempEdits);
_tempEdits = 0;
context.perform(
ReplaceDrawEntities(node),
annotation
);
tempEdits = 0;
context.enter(mode);
};
@@ -289,8 +292,8 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// If the way has enough nodes to be valid, it's selected.
// Otherwise, delete everything and return to browse mode.
drawWay.finish = function() {
context.pop(tempEdits);
tempEdits = 0;
context.pop(_tempEdits);
_tempEdits = 0;
var way = context.hasEntity(wayId);
if (!way || way.isDegenerate()) {
@@ -308,8 +311,8 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
// Cancel the draw operation, delete everything, and return to browse mode.
drawWay.cancel = function() {
context.pop(tempEdits);
tempEdits = 0;
context.pop(_tempEdits);
_tempEdits = 0;
while (context.graph() !== startGraph) {
context.pop();
@@ -323,20 +326,15 @@ export function behaviorDrawWay(context, wayId, index, mode, startGraph) {
};
// drawWay.activeIDs = function() {
// if (!arguments.length) return _activeIDs;
// // no assign
// return drawWay;
// };
drawWay.activeID = function() {
if (!arguments.length) return _activeID;
if (!arguments.length) return end.id;
// no assign
return drawWay;
};
drawWay.tail = function(text) {
draw.tail(text);
behavior.tail(text);
return drawWay;
};
-7
View File
@@ -255,13 +255,6 @@ export function coreContext() {
return [];
}
};
// context.activeIDs = function() {
// if (mode && mode.activeIDs) {
// return mode.activeIDs();
// } else {
// return [];
// }
// };
context.activeID = function() {
return mode && mode.activeID && mode.activeID();
};
+43 -78
View File
@@ -12,25 +12,10 @@ import {
actionNoop
} from '../actions';
import {
behaviorEdit,
behaviorHover,
behaviorDrag
} from '../behavior';
import {
modeBrowse,
modeSelect
} from './index';
import {
geoChooseEdge,
geoVecSubtract,
geoViewportEdge
} from '../geo';
import { behaviorEdit, behaviorHover, behaviorDrag } from '../behavior';
import { geoChooseEdge, geoVecSubtract, geoViewportEdge } from '../geo';
import { modeBrowse, modeSelect } from './index';
import { osmNode } from '../osm';
import { utilEntitySelector } from '../util';
import { uiFlash } from '../ui';
@@ -39,16 +24,15 @@ export function modeDragNode(context) {
id: 'drag-node',
button: 'browse'
};
var hover = behaviorHover(context).altDisables(true).on('hover', context.ui().sidebar.hover);
var hover = behaviorHover(context).altDisables(true)
.on('hover', context.ui().sidebar.hover);
var edit = behaviorEdit(context);
var _nudgeInterval;
var _restoreSelectedIDs = [];
// var _activeIDs = [];
var _activeID;
var _wasMidpoint = false;
var _isCancelled = false;
var _dragEntity;
var _activeEntity;
var _lastLoc;
@@ -94,7 +78,7 @@ export function modeDragNode(context) {
if (hasHidden) {
uiFlash().text(t('modes.drag_node.connected_to_hidden'))();
}
return behavior.cancel();
return drag.cancel();
}
if (_wasMidpoint) {
@@ -103,20 +87,15 @@ export function modeDragNode(context) {
context.perform(actionAddMidpoint(midpoint, entity));
var vertex = context.surface().selectAll('.' + entity.id);
behavior.target(vertex.node(), entity);
drag.target(vertex.node(), entity);
} else {
context.perform(actionNoop());
}
_dragEntity = entity;
// `.active` elements have `pointer-events: none`.
// This prevents the node or vertex being dragged from trying to connect to itself.
// _activeIDs = context.graph().parentWays(entity).map(function(parent) { return parent.id; });
// _activeIDs.push(entity.id);
_activeID = entity.id;
setActiveElements();
_activeEntity = entity;
context.surface().selectAll('.' + _activeEntity.id)
.classed('active', true);
context.enter(mode);
}
@@ -127,8 +106,7 @@ export function modeDragNode(context) {
if (!event || event.altKey || !d3_select(event.target).classed('target')) {
return {};
} else {
var d = event.target.__data__;
return (d && d.id && context.hasEntity(d.id)) || {};
return event.target.__data__ || {};
}
}
@@ -140,20 +118,21 @@ export function modeDragNode(context) {
var currMouse = geoVecSubtract(currPoint, nudge);
var loc = context.projection.invert(currMouse);
if (!_nudgeInterval) {
// If we're not nudging at the edge of the viewport, try to snap..
// See also `behavior/draw.js click()`
if (!_nudgeInterval) { // If not nudging at the edge of the viewport, try to snap..
// related code
// - `mode/drag_node.js` `doMode()`
// - `behavior/draw.js` `click()`
// - `behavior/draw_way.js` `move()`
var d = datum();
var target = d && d.id && context.hasEntity(d.id);
// Snap to a node (not self)
if (d.type === 'node' && d.id !== entity.id) {
loc = d.loc;
// Snap to a way
} else if (d.type === 'way') {
var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection);
// (not along a segment adjacent to self)
if (entity.id !== d.nodes[choice.index - 1] && entity.id !== d.nodes[choice.index]) {
if (target && target.type === 'node') {
loc = target.loc;
} else if (target && target.type === 'way') {
var choice = geoChooseEdge(
context.childNodes(target), context.mouse(), context.projection, entity.id
);
if (choice) {
loc = choice.loc;
}
}
@@ -188,18 +167,22 @@ export function modeDragNode(context) {
if (_isCancelled) return;
var d = datum();
var target = d && d.id && context.hasEntity(d.id);
if (d.type === 'way') {
var choice = geoChooseEdge(context.childNodes(d), context.mouse(), context.projection);
if (target && target.type === 'way') {
var choice = geoChooseEdge(context.childNodes(target), context.mouse(), context.projection, entity.id);
context.replace(
actionAddMidpoint({ loc: choice.loc, edge: [d.nodes[choice.index - 1], d.nodes[choice.index]] }, entity),
connectAnnotation(d)
actionAddMidpoint({
loc: choice.loc,
edge: [target.nodes[choice.index - 1], target.nodes[choice.index]]
}, entity),
connectAnnotation(target)
);
} else if (d.type === 'node' && d.id !== entity.id) {
} else if (target && target.type === 'node') {
context.replace(
actionConnect([d.id, entity.id]),
connectAnnotation(d)
actionConnect([target.id, entity.id]),
connectAnnotation(target)
);
} else if (_wasMidpoint) {
@@ -228,20 +211,12 @@ export function modeDragNode(context) {
function cancel() {
behavior.cancel();
drag.cancel();
context.enter(modeBrowse(context));
}
function setActiveElements() {
// context.surface().selectAll(utilEntitySelector(_activeIDs))
// .classed('active', true);
context.surface().selectAll('.' + _activeID)
.classed('active', true);
}
var behavior = behaviorDrag()
var drag = behaviorDrag()
.selector('.layer-points-targets .target')
.surface(d3_select('#map').node())
.origin(origin)
@@ -256,11 +231,6 @@ export function modeDragNode(context) {
context.history()
.on('undone.drag-node', cancel);
context.map()
.on('drawn.drag-node', setActiveElements);
setActiveElements();
};
@@ -275,8 +245,8 @@ export function modeDragNode(context) {
context.map()
.on('drawn.drag-node', null);
// _activeIDs = [];
_activeID = null;
_activeEntity = null;
context.surface()
.selectAll('.active')
.classed('active', false);
@@ -286,19 +256,14 @@ export function modeDragNode(context) {
mode.selectedIDs = function() {
if (!arguments.length) return _dragEntity ? [_dragEntity.id] : [];
if (!arguments.length) return _activeEntity ? [_activeEntity.id] : [];
// no assign
return mode;
};
// mode.activeIDs = function() {
// if (!arguments.length) return _activeIDs;
// // no assign
// return mode;
// };
mode.activeID = function() {
if (!arguments.length) return _activeID;
if (!arguments.length) return _activeEntity && _activeEntity.id;
// no assign
return mode;
};
@@ -311,7 +276,7 @@ export function modeDragNode(context) {
};
mode.behavior = behavior;
mode.behavior = drag;
return mode;
+1 -3
View File
@@ -43,9 +43,7 @@ export function modeDrawArea(context, wayId, startGraph) {
return [wayId];
};
// mode.activeIDs = function() {
// return (behavior && behavior.activeIDs()) || [];
// };
mode.activeID = function() {
return (behavior && behavior.activeID()) || [];
};
+1 -3
View File
@@ -41,9 +41,7 @@ export function modeDrawLine(context, wayId, startGraph, affix) {
return [wayId];
};
// mode.activeIDs = function() {
// return (behavior && behavior.activeIDs()) || [];
// };
mode.activeID = function() {
return (behavior && behavior.activeID()) || [];
};
-1
View File
@@ -31,7 +31,6 @@ export function svgPoints(projection, context) {
var fillClass = context.getDebug('target') ? 'pink ' : 'nocolor ';
var passive = entities.filter(function(d) {
return d.id !== context.activeID();
// return context.activeIDs().indexOf(d.id) === -1;
});
var targets = selection.selectAll('.point.target')