Extract Draw behavior

Fixed some bugs but introduced others.
This commit is contained in:
John Firebaugh
2013-01-24 11:04:06 -05:00
parent 4d7478972a
commit 18c7267010
11 changed files with 343 additions and 338 deletions
+2
View File
@@ -91,6 +91,8 @@
<script src='js/id/behavior/drag_midpoint.js'></script>
<script src='js/id/behavior/drag_node.js'></script>
<script src='js/id/behavior/drag_way.js'></script>
<script src='js/id/behavior/draw.js'></script>
<script src='js/id/behavior/draw_way.js'></script>
<script src='js/id/behavior/hover.js'></script>
<script src='js/id/modes.js'></script>
+54
View File
@@ -0,0 +1,54 @@
iD.behavior.Draw = function () {
var event = d3.dispatch('move', 'add', 'drop', 'cancel', 'finish'),
keybinding = d3.keybinding('draw');
function draw(selection) {
function mousemove() {
event.move();
}
function click() {
event.add();
}
function backspace() {
d3.event.preventDefault();
event.drop();
}
function del() {
d3.event.preventDefault();
event.cancel();
}
function ret() {
d3.event.preventDefault();
event.finish();
}
selection
.on('mousemove.draw', mousemove)
.on('click.draw', click);
keybinding
.on('⌫', backspace)
.on('⌦', del)
.on('⎋', ret)
.on('↩', ret);
d3.select(document)
.call(keybinding);
return draw;
}
draw.off = function(selection) {
selection
.on('mousemove.draw', null)
.on('click.draw', null);
keybinding.off();
};
return d3.rebind(draw, event, 'on');
};
+146
View File
@@ -0,0 +1,146 @@
iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode) {
var map = mode.map,
history = mode.history,
controller = mode.controller,
event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'),
way = mode.history.graph().entity(wayId),
nodeId = way.nodes[index],
hover, draw;
function move() {
history.replace(
iD.actions.MoveNode(nodeId, map.mouseCoordinates()),
history.undoAnnotation());
}
function add() {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === headId) {
event.addHead(datum);
} else if (datum.id === tailId) {
event.addTail(datum);
} else if (datum.type === 'node' && datum.id !== nodeId) {
event.addNode(datum);
} else if (datum.type === 'way') {
var choice = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map);
event.addWay(datum, choice.loc, choice.index);
} else if (datum.midpoint) {
var way = history.graph().entity(datum.way);
event.addWay(way, datum.loc, datum.index);
} else {
event.add(map.mouseCoordinates());
}
}
function undone() {
var way = history.graph().entity(wayId);
if (way) {
controller.enter(mode);
} else {
controller.enter(iD.modes.Browse());
}
}
var drawWay = function(surface) {
map.fastEnable(false)
.minzoom(16)
.dblclickEnable(false);
surface.call(hover)
.call(draw)
.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === nodeId; })
.classed('active', true);
history.on('undone.draw', undone);
};
drawWay.off = function(surface) {
map.fastEnable(true)
.minzoom(0)
.tail(false);
window.setTimeout(function() {
map.dblclickEnable(true);
}, 1000);
surface.call(hover.off)
.call(draw.off)
.selectAll('.way, .node')
.classed('active', false);
history.on('undone.draw', null);
};
// Connect the way to an existing node. Continue drawing, or enter the optional `newMode`.
drawWay.addNode = function(node, annotation, newMode) {
history.perform(
iD.actions.AddWayNode(wayId, node.id, index),
annotation);
controller.enter(newMode || mode);
};
// Connect the way to an existing way.
drawWay.addWay = function(way, loc, wayIndex, annotation) {
var newNode = iD.Node({loc: loc});
history.perform(
iD.actions.AddNode(newNode),
iD.actions.AddWayNode(wayId, newNode.id, index),
iD.actions.AddWayNode(way.id, newNode.id, wayIndex),
annotation);
controller.enter(mode);
};
// Accept the current position of the temporary node and continue drawing.
drawWay.add = function(loc, annotation) {
var newNode = iD.Node({loc: loc});
history.perform(
iD.actions.AddNode(newNode),
iD.actions.AddWayNode(wayId, newNode.id, index),
annotation);
controller.enter(mode);
};
// Remove the temporary node and the last connected node but continue drawing.
drawWay.drop = function() {
history.undo();
};
// Finish the draw operation, removing the temporary node. If the way has enough
// nodes to be valid, it's selected. Otherwise, return to browse mode.
drawWay.finish = function() {
history.replace(
iD.actions.DeleteNode(nodeId),
history.undoAnnotation());
var way = history.graph().entity(wayId);
if (way) {
controller.enter(iD.modes.Select(way, true));
} else {
controller.enter(iD.modes.Browse());
}
};
// Cancel the draw operation and return to browse, deleting everything drawn.
drawWay.cancel = function() {
history.perform(iD.actions.DeleteWay(wayId), 'cancelled drawing');
controller.enter(iD.modes.Browse());
};
hover = iD.behavior.Hover();
draw = iD.behavior.Draw()
.on('move', move)
.on('add', add)
.on('drop', drawWay.drop)
.on('cancel', drawWay.cancel)
.on('finish', drawWay.finish);
return d3.rebind(drawWay, event, 'on');
};
+1 -1
View File
@@ -8,7 +8,7 @@ iD.History = function() {
var annotation;
if (_.isString(_.last(actions))) {
if (!_.isFunction(_.last(actions))) {
annotation = actions.pop();
}
+30 -15
View File
@@ -6,30 +6,33 @@ iD.modes.AddArea = function() {
description: 'Add parks, buildings, lakes, or other areas to the map.'
};
var keybinding = d3.keybinding('add-area');
var behavior;
mode.enter = function() {
var map = mode.map,
surface = map.surface,
history = mode.history,
controller = mode.controller;
map.dblclickEnable(false)
.tail('Click on the map to start drawing an area, like a park, lake, or building.');
map.surface.on('click.addarea', function() {
function add() {
var datum = d3.select(d3.event.target).datum() || {},
way = iD.Way({tags: { area: 'yes' }});
way = iD.Way({tags: { area: 'yes' }}),
node;
if (datum.type === 'node') {
// start from an existing node
node = datum;
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, datum.id),
iD.actions.AddWayNode(way.id, datum.id));
iD.actions.AddWayNode(way.id, node.id),
iD.actions.AddWayNode(way.id, node.id));
} else {
// start from a new node
var node = iD.Node({loc: map.mouseCoordinates()});
node = iD.Node({loc: map.mouseCoordinates()});
history.perform(
iD.actions.AddWay(way),
iD.actions.AddNode(node),
@@ -37,24 +40,36 @@ iD.modes.AddArea = function() {
iD.actions.AddWayNode(way.id, node.id));
}
node = iD.Node({loc: node.loc});
history.replace(
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id, -1),
'started an area');
controller.enter(iD.modes.DrawArea(way.id));
});
}
keybinding.on('⎋', function() {
function cancel() {
controller.exit();
});
}
d3.select(document)
.call(keybinding);
behavior = iD.behavior.Draw()
.on('add', add)
.on('cancel', cancel)
.on('finish', cancel)
(surface);
};
mode.exit = function() {
var map = mode.map,
surface = map.surface;
window.setTimeout(function() {
mode.map.dblclickEnable(true);
map.dblclickEnable(true);
}, 1000);
mode.map.tail(false);
mode.map.surface.on('click.addarea', null);
keybinding.off();
map.tail(false);
behavior.off(surface);
};
return mode;
+34 -19
View File
@@ -6,37 +6,38 @@ iD.modes.AddLine = function() {
description: 'Lines can be highways, streets, pedestrian paths, or even canals.'
};
var keybinding = d3.keybinding('add-line');
var behavior;
mode.enter = function() {
var map = mode.map,
surface = map.surface,
graph = map.history().graph(),
node,
history = mode.history,
controller = mode.controller;
map.dblclickEnable(false)
.tail('Click on the map to start drawing an road, path, or route.');
map.surface.on('click.addline', function() {
function add() {
var datum = d3.select(d3.event.target).datum() || {},
way = iD.Way({ tags: { highway: 'residential' } }),
direction = 'forward';
direction = 'forward',
node;
if (datum.type === 'node') {
// continue an existing way
var id = datum.id;
var parents = history.graph(graph).parentWays(datum);
node = datum;
var parents = history.graph(graph).parentWays(node);
var isLine = parents.length && parents[0].geometry(graph) === 'line';
if (isLine && parents[0].nodes[0] === id ) {
if (isLine && parents[0].first() === node.id) {
way = parents[0];
direction = 'backward';
} else if (isLine && _.last(parents[0].nodes) === id) {
} else if (isLine && parents[0].last() === node.id) {
way = parents[0];
} else {
history.perform(
iD.actions.AddWay(way),
iD.actions.AddWayNode(way.id, datum.id));
iD.actions.AddWayNode(way.id, node.id));
}
} else if (datum.type === 'way') {
@@ -60,22 +61,36 @@ iD.modes.AddLine = function() {
iD.actions.AddWayNode(way.id, node.id));
}
controller.enter(iD.modes.DrawLine(way.id, direction));
});
var index = (direction === 'forward') ? way.nodes.length : 0,
keybinding.on('⎋', function() {
node = iD.Node({loc: node.loc});
history.replace(
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id, index),
'started a line');
controller.enter(iD.modes.DrawLine(way.id, direction, node));
}
function cancel() {
controller.exit();
});
}
d3.select(document)
.call(keybinding);
behavior = iD.behavior.Draw()
.on('add', add)
.on('cancel', cancel)
.on('finish', cancel)
(surface);
};
mode.exit = function() {
mode.map.dblclickEnable(true);
mode.map.tail(false);
mode.map.surface.on('click.addline', null);
keybinding.off();
var map = mode.map,
surface = map.surface;
map.dblclickEnable(true);
map.tail(false);
behavior.off(surface);
};
return mode;
+16 -10
View File
@@ -5,16 +5,17 @@ iD.modes.AddPoint = function() {
description: 'Restaurants, monuments, and postal boxes are points.'
};
var keybinding = d3.keybinding('add-point');
var behavior;
mode.enter = function() {
var map = mode.map,
surface = map.surface,
history = mode.history,
controller = mode.controller;
map.tail('Click on the map to add a point.');
map.surface.on('click.addpoint', function() {
function add() {
var node = iD.Node({loc: map.mouseCoordinates()});
history.perform(
@@ -22,20 +23,25 @@ iD.modes.AddPoint = function() {
'added a point');
controller.enter(iD.modes.Select(node, true));
});
}
keybinding.on('⎋', function() {
function cancel() {
controller.exit();
});
}
d3.select(document)
.call(keybinding);
behavior = iD.behavior.Draw()
.on('add', add)
.on('cancel', cancel)
.on('finish', cancel)
(surface);
};
mode.exit = function() {
mode.map.tail(false);
mode.map.surface.on('click.addpoint', null);
keybinding.off();
var map = mode.map,
surface = map.surface;
map.tail(false);
behavior.off(surface);
};
return mode;
+19 -127
View File
@@ -4,147 +4,39 @@ iD.modes.DrawArea = function(wayId) {
id: 'draw-area'
};
var keybinding = d3.keybinding('draw-area');
var behavior;
mode.enter = function() {
var map = mode.map,
surface = map.surface,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
index = way.nodes.length - 1,
var way = mode.history.graph().entity(wayId),
index = way.nodes.length - 2,
headId = way.nodes[index - 1],
tailId = way.first(),
node = iD.Node({loc: map.mouseCoordinates()});
tailId = way.first();
map.dblclickEnable(false)
.fastEnable(false);
map.tail('Click to add points to your area. Click the first point to finish the area.');
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(way.id, node.id, index));
surface.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === node.id; })
.classed('active', true);
function ReplaceTemporaryNode(replacementId) {
return function(graph) {
graph = graph.replace(graph.entity(wayId).updateNode(replacementId, index));
graph = graph.remove(node);
return graph;
}
function addHeadTail() {
behavior.finish();
}
function mousemove() {
history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
function addNode(node) {
behavior.addNode(node, way.nodes.length > 2 ? 'added to an area' : '');
}
function click() {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === tailId || datum.id === headId) {
if (way.nodes.length > 3) {
history.undo();
controller.enter(iD.modes.Select(way, true));
} else {
// Areas with less than 3 nodes gets deleted
history.replace(iD.actions.DeleteWay(way.id));
controller.enter(iD.modes.Browse());
}
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
ReplaceTemporaryNode(datum.id),
way.nodes.length > 2 ? 'added to an area' : '');
controller.enter(iD.modes.DrawArea(wayId));
} else {
history.replace(
iD.actions.Noop(),
way.nodes.length > 2 ? 'added to an area' : '');
controller.enter(iD.modes.DrawArea(wayId));
}
function add(loc) {
behavior.add(loc, way.nodes.length > 2 ? 'added to an area' : '');
}
function backspace() {
d3.event.preventDefault();
behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode)
.on('addHead', addHeadTail)
.on('addTail', addHeadTail)
.on('addNode', addNode)
.on('addWay', add)
.on('add', add);
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
if (history.graph().entity(wayId)) {
controller.enter(iD.modes.DrawArea(wayId));
} else {
// The way was deleted because it had too few nodes.
controller.enter(iD.modes.Browse());
}
}
function del() {
d3.event.preventDefault();
history.replace(iD.actions.DeleteWay(wayId));
controller.enter(iD.modes.Browse());
}
function ret() {
d3.event.preventDefault();
history.replace(iD.actions.DeleteNode(node.id));
if (history.graph().entity(wayId)) {
controller.enter(iD.modes.Select(way, true));
} else {
// The way was deleted because it had too few nodes.
controller.enter(iD.modes.Browse());
}
}
surface
.on('mousemove.drawarea', mousemove)
.on('click.drawarea', click);
keybinding
.on('⌫', backspace)
.on('⌦', del)
.on('⎋', ret)
.on('↩', ret);
d3.select(document)
.call(keybinding);
history.on('undone.drawarea', function () {
controller.enter(iD.modes.Browse());
});
mode.map.surface.call(behavior);
mode.map.tail('Click to add points to your area. Click the first point to finish the area.');
};
mode.exit = function() {
var map = mode.map,
surface = map.surface,
history = mode.history;
surface.selectAll('.way, .node')
.classed('active', false);
map.tail(false);
map.fastEnable(true);
surface
.on('mousemove.drawarea', null)
.on('click.drawarea', null);
keybinding.off();
history.on('undone.drawarea', null);
window.setTimeout(function() {
mode.map.dblclickEnable(true);
}, 1000);
mode.map.surface.call(behavior.off);
};
return mode;
+38 -166
View File
@@ -4,182 +4,54 @@ iD.modes.DrawLine = function(wayId, direction) {
id: 'draw-line'
};
var keybinding = d3.keybinding('draw-line');
var behavior;
mode.enter = function() {
var map = mode.map,
surface = map.surface,
history = mode.history,
controller = mode.controller,
way = history.graph().entity(wayId),
node = iD.Node({loc: map.mouseCoordinates()}),
index = (direction === 'forward') ? way.nodes.length : 0,
headId = (direction === 'forward') ? way.last() : way.first(),
var way = mode.history.graph().entity(wayId),
index = (direction === 'forward') ? way.nodes.length - 1 : 0,
headId = (direction === 'forward') ? way.nodes[index - 1] : way.nodes[index + 1],
tailId = (direction === 'forward') ? way.first() : way.last();
iD.behavior.Hover()(surface);
function addHead() {
behavior.finish();
}
map.dblclickEnable(false)
.fastEnable(false)
.tail('Click to add more points to the line. ' +
function addTail(node) {
// connect the way in a loop
if (way.nodes.length > 2) {
behavior.addNode(node, 'added to a line', iD.modes.Select(way, true))
} else {
behavior.cancel();
}
}
function addNode(node) {
behavior.addNode(node, 'added to a line');
}
function addWay(way, loc, index) {
behavior.addWay(way, loc, index, 'added to a line');
}
function add(loc) {
behavior.add(loc, 'added to a line');
}
behavior = iD.behavior.DrawWay(wayId, headId, tailId, index, mode)
.on('addHead', addHead)
.on('addTail', addTail)
.on('addNode', addNode)
.on('addWay', addWay)
.on('add', add);
mode.map.surface.call(behavior);
mode.map.tail('Click to add more points to the line. ' +
'Click on other lines to connect to them, and double-click to ' +
'end the line.');
map.minzoom(16);
history.perform(
iD.actions.AddNode(node),
iD.actions.AddWayNode(wayId, node.id, index));
surface.selectAll('.way, .node')
.filter(function (d) { return d.id === wayId || d.id === node.id; })
.classed('active', true);
function ReplaceTemporaryNode(replacementId) {
return function(graph) {
graph = graph.replace(graph.entity(wayId).updateNode(replacementId, index));
graph = graph.remove(node);
return graph;
}
}
function mousemove() {
history.replace(iD.actions.MoveNode(node.id, map.mouseCoordinates()));
}
function click() {
var datum = d3.select(d3.event.target).datum() || {};
if (datum.id === tailId) {
// connect the way in a loop
if (way.nodes.length > 2) {
history.replace(
ReplaceTemporaryNode(tailId),
'added to a line');
controller.enter(iD.modes.Select(way, true));
} else {
history.replace(iD.actions.DeleteWay(way.id));
controller.enter(iD.modes.Browse());
}
} else if (datum.id === headId) {
// finish the way
history.undo();
controller.enter(iD.modes.Select(way, true));
} else if (datum.type === 'node' && datum.id !== node.id) {
// connect the way to an existing node
history.replace(
ReplaceTemporaryNode(datum.id),
'added to a line');
controller.enter(iD.modes.DrawLine(wayId, direction));
} else if (datum.type === 'way' || datum.midpoint) {
var choice;
// connect the way to an existing way
if (datum.midpoint) {
// if clicked on midpoint
datum.id = datum.way;
choice = datum;
} else {
choice = iD.geo.chooseIndex(datum, d3.mouse(surface.node()), map);
}
history.replace(
iD.actions.MoveNode(node.id, choice.loc),
iD.actions.AddWayNode(datum.id, node.id, choice.index),
'added to a line');
controller.enter(iD.modes.DrawLine(wayId, direction));
} else {
history.replace(
iD.actions.Noop(),
'added to a line');
controller.enter(iD.modes.DrawLine(wayId, direction));
}
}
function backspace() {
d3.event.preventDefault();
history.replace(
iD.actions.DeleteNode(node.id),
iD.actions.DeleteNode(headId));
if (history.graph().entity(wayId)) {
controller.enter(iD.modes.DrawLine(wayId, direction));
} else {
// The way was deleted because it had too few nodes.
controller.enter(iD.modes.Browse());
}
}
function del() {
d3.event.preventDefault();
history.replace(iD.actions.DeleteWay(wayId));
controller.enter(iD.modes.Browse());
}
function ret() {
d3.event.preventDefault();
history.replace(iD.actions.DeleteNode(node.id));
if (history.graph().entity(wayId)) {
controller.enter(iD.modes.Select(way, true));
} else {
// The way was deleted because it had too few nodes.
controller.enter(iD.modes.Browse());
}
}
surface
.on('mousemove.drawline', mousemove)
.on('click.drawline', click);
keybinding
.on('⌫', backspace)
.on('⌦', del)
.on('⎋', ret)
.on('↩', ret);
d3.select(document)
.call(keybinding);
history.on('undone.drawline', function () {
controller.enter(iD.modes.Browse());
});
};
mode.exit = function() {
var map = mode.map,
surface = map.surface,
history = mode.history;
surface.selectAll('.way, .node')
.classed('active', false);
map.tail(false);
map.fastEnable(true);
map.minzoom(0);
surface
.on('mousemove.drawline', null)
.on('click.drawline', null);
keybinding.off();
history.on('undone.drawline', null);
window.setTimeout(function() {
mode.map.dblclickEnable(true);
}, 1000);
mode.map.surface.call(behavior.off);
};
return mode;
+1
View File
@@ -341,6 +341,7 @@ iD.Map = function() {
map.minzoom = function(_) {
if (!arguments.length) return minzoom;
minzoom = _;
return map;
};
map.history = function (_) {
+2
View File
@@ -86,6 +86,8 @@
<script src='../js/id/behavior/drag_midpoint.js'></script>
<script src='../js/id/behavior/drag_node.js'></script>
<script src='../js/id/behavior/drag_way.js'></script>
<script src='../js/id/behavior/draw.js'></script>
<script src='../js/id/behavior/draw_way.js'></script>
<script src='../js/id/behavior/hover.js'></script>
<script src='../js/id/modes.js'></script>