From b37ef978f45af985538b176dd417f12b63e8e368 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 25 Jan 2013 11:47:05 -0500 Subject: [PATCH] Snap to ways/vertices/midpoints when drawing (#240) Also add anti-snapping behavior when option key is down. --- css/map.css | 26 ++++---- js/id/behavior/add_way.js | 14 ++--- js/id/behavior/draw.js | 114 +++++++++++++++++++++++------------- js/id/behavior/draw_way.js | 26 ++++---- js/id/behavior/hover.js | 5 +- test/spec/behavior/hover.js | 19 +++++- 6 files changed, 126 insertions(+), 78 deletions(-) diff --git a/css/map.css b/css/map.css index 8f235bb18..7b3038d6c 100644 --- a/css/map.css +++ b/css/map.css @@ -16,7 +16,7 @@ g.point .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.point.hover .shadow { +.behavior-hover g.point.hover .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -106,7 +106,7 @@ g.vertex .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.vertex.hover .shadow { +.behavior-hover g.vertex.hover .shadow { fill: #E96666; fill-opacity: 0.3; } @@ -120,7 +120,7 @@ g.vertex.selected .shadow { g.midpoint .fill { fill:#aaa; } -g.midpoint .fill.hover { +.behavior-hover g.midpoint .fill.hover { fill:#fff; stroke:#000; } @@ -133,7 +133,7 @@ g.midpoint .shadow { transition: transform 100ms linear; -moz-transition: fill 100ms linear; } -g.midpoint .shadow.hover { +.behavior-hover g.midpoint .shadow.hover { fill:#E96666; fill-opacity: 0.3; } @@ -161,7 +161,7 @@ path.shadow { -webkit-transition: stroke 100ms linear; } -path.shadow.hover { +.behavior-hover path.shadow.hover { stroke: #E96666; stroke-opacity: 0.3; } @@ -691,17 +691,17 @@ text.point.tag-amenity { cursor:url(../img/cursor-draw.png) 9 9, auto; } -.mode-draw-line .way, -.mode-draw-area .way, -.mode-add-line .way, -.mode-add-area .way { +.mode-draw-line .behavior-hover .way, +.mode-draw-area .behavior-hover .way, +.mode-add-line .behavior-hover .way, +.mode-add-area .behavior-hover .way { cursor:url(../img/cursor-draw-connect-line.png) 9 9, auto; } -.mode-draw-line .vertex, -.mode-draw-area .vertex, -.mode-add-line .vertex, -.mode-add-area .vertex { +.mode-draw-line .behavior-hover .vertex, +.mode-draw-area .behavior-hover .vertex, +.mode-add-line .behavior-hover .vertex, +.mode-add-area .behavior-hover .vertex { cursor:url(../img/cursor-draw-connect-vertex.png) 9 9, auto; } diff --git a/js/id/behavior/add_way.js b/js/id/behavior/add_way.js index 7c02355fb..021377890 100644 --- a/js/id/behavior/add_way.js +++ b/js/id/behavior/add_way.js @@ -3,11 +3,9 @@ iD.behavior.AddWay = function(mode) { history = mode.history, controller = mode.controller, event = d3.dispatch('startFromNode', 'startFromWay', 'start'), - hover, draw; - - function add() { - var datum = d3.select(d3.event.target).datum() || {}; + draw; + function add(datum) { if (datum.type === 'node') { event.startFromNode(datum); } else if (datum.type === 'way') { @@ -26,8 +24,7 @@ iD.behavior.AddWay = function(mode) { .minzoom(16) .dblclickEnable(false); - surface.call(hover) - .call(draw); + surface.call(draw); }; addWay.off = function(surface) { @@ -39,16 +36,13 @@ iD.behavior.AddWay = function(mode) { map.dblclickEnable(true); }, 1000); - surface.call(hover.off) - .call(draw.off); + surface.call(draw.off); }; addWay.cancel = function() { controller.exit(); }; - hover = iD.behavior.Hover(); - draw = iD.behavior.Draw() .on('add', add) .on('cancel', addWay.cancel) diff --git a/js/id/behavior/draw.js b/js/id/behavior/draw.js index 2725b8111..4d0153505 100644 --- a/js/id/behavior/draw.js +++ b/js/id/behavior/draw.js @@ -1,45 +1,64 @@ iD.behavior.Draw = function () { var event = d3.dispatch('move', 'add', 'undo', 'cancel', 'finish'), keybinding = d3.keybinding('draw'), - down; + down, surface, hover; + + function datum() { + if (d3.event.altKey) { + return {}; + } else { + return d3.event.target.__data__ || {}; + } + } + + function mousedown() { + down = true; + } + + function mouseup() { + down = false; + } + + function mousemove() { + if (!down) { + event.move(datum()); + } + } + + function click() { + event.add(datum()); + } + + function keydown() { + if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { + surface.call(hover.off); + } + } + + function keyup() { + if (d3.event.keyCode === d3.keybinding.modifierCodes.alt) { + surface.call(hover); + } + } + + function backspace() { + d3.event.preventDefault(); + event.undo(); + } + + function del() { + d3.event.preventDefault(); + event.cancel(); + } + + function ret() { + d3.event.preventDefault(); + event.finish(); + } function draw(selection) { - function mousemove() { - if (!down) event.move(); - } - - function click() { - event.add(); - } - - function mousedown() { - down = true; - } - - function mouseup() { - down = false; - } - - function backspace() { - d3.event.preventDefault(); - event.undo(); - } - - function del() { - d3.event.preventDefault(); - event.cancel(); - } - - function ret() { - d3.event.preventDefault(); - event.finish(); - } - - selection - .on('mousedown.draw', mousedown) - .on('mouseup.draw', mouseup) - .on('mousemove.draw', mousemove) - .on('click.draw', click); + surface = selection; + hover = iD.behavior.Hover(); keybinding .on('⌫', backspace) @@ -47,18 +66,33 @@ iD.behavior.Draw = function () { .on('⎋', ret) .on('↩', ret); + selection + .on('mousedown.draw', mousedown) + .on('mouseup.draw', mouseup) + .on('mousemove.draw', mousemove) + .on('click.draw', click) + .call(hover); + d3.select(document) - .call(keybinding); + .call(keybinding) + .on('keydown.draw', keydown) + .on('keyup.draw', keyup); return draw; } draw.off = function(selection) { selection + .on('mousedown.draw', null) + .on('mouseup.draw', null) .on('mousemove.draw', null) - .on('click.draw', null); + .on('click.draw', null) + .call(hover.off); - keybinding.off(); + d3.select(document) + .call(keybinding.off) + .on('keydown.draw', null) + .on('keyup.draw', null); }; return d3.rebind(draw, event, 'on'); diff --git a/js/id/behavior/draw_way.js b/js/id/behavior/draw_way.js index 0e5bcf4fd..63526df7a 100644 --- a/js/id/behavior/draw_way.js +++ b/js/id/behavior/draw_way.js @@ -5,7 +5,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { event = d3.dispatch('add', 'addHead', 'addTail', 'addNode', 'addWay'), way = mode.history.graph().entity(wayId), finished = false, - hover, draw; + draw; var node = iD.Node({loc: map.mouseCoordinates()}), nodeId = node.id; @@ -14,13 +14,19 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { iD.actions.AddNode(node), iD.actions.AddWayNode(wayId, node.id, index)); - function move() { - history.replace(iD.actions.MoveNode(nodeId, map.mouseCoordinates())); + function move(datum) { + var loc = map.mouseCoordinates(); + + if (datum.type === 'node' || datum.midpoint) { + loc = datum.loc; + } else if (datum.type === 'way') { + loc = iD.geo.chooseIndex(datum, d3.mouse(map.surface.node()), map).loc; + } + + history.replace(iD.actions.MoveNode(nodeId, loc)); } - function add() { - var datum = d3.select(d3.event.target).datum() || {}; - + function add(datum) { if (datum.id === headId) { event.addHead(datum); } else if (datum.id === tailId) { @@ -47,8 +53,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { .minzoom(16) .dblclickEnable(false); - surface.call(hover) - .call(draw) + surface.call(draw) .selectAll('.way, .node') .filter(function (d) { return d.id === wayId || d.id === nodeId; }) .classed('active', true); @@ -68,8 +73,7 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { map.dblclickEnable(true); }, 1000); - surface.call(hover.off) - .call(draw.off) + surface.call(draw.off) .selectAll('.way, .node') .classed('active', false); @@ -145,8 +149,6 @@ iD.behavior.DrawWay = function(wayId, headId, tailId, index, mode, baseGraph) { controller.enter(iD.modes.Browse()); }; - hover = iD.behavior.Hover(); - draw = iD.behavior.Draw() .on('move', move) .on('add', add) diff --git a/js/id/behavior/hover.js b/js/id/behavior/hover.js index 9d50cbbd0..e4d33dd98 100644 --- a/js/id/behavior/hover.js +++ b/js/id/behavior/hover.js @@ -9,6 +9,8 @@ */ iD.behavior.Hover = function () { var hover = function(selection) { + selection.classed('behavior-hover', true); + selection.on('mouseover.hover', function () { var datum = d3.event.target.__data__; if (datum) { @@ -25,7 +27,8 @@ iD.behavior.Hover = function () { }; hover.off = function(selection) { - selection.on('mouseover.hover', null) + selection.classed('behavior-hover', false) + .on('mouseover.hover', null) .on('mouseout.hover', null); }; diff --git a/test/spec/behavior/hover.js b/test/spec/behavior/hover.js index 817654d59..62d301d19 100644 --- a/test/spec/behavior/hover.js +++ b/test/spec/behavior/hover.js @@ -9,8 +9,23 @@ describe("iD.behavior.Hover", function() { container.remove(); }); + describe("#on", function () { + it("adds the .behavior-hover class to the selection", function () { + container.call(iD.behavior.Hover()); + expect(container).to.be.classed('behavior-hover') + }); + }); + + describe("#off", function () { + it("removes the .behavior-hover class from the selection", function () { + container.classed('behavior-hover', true); + container.call(iD.behavior.Hover().off); + expect(container).not.to.be.classed('behavior-hover') + }); + }); + describe("mouseover", function () { - it("adds the 'hover' class to all elements to which the same datum is bound", function () { + it("adds the .hover class to all elements to which the same datum is bound", function () { container.selectAll('span') .data(['a', 'b', 'a', 'b']) .enter().append('span').attr('class', Object); @@ -24,7 +39,7 @@ describe("iD.behavior.Hover", function() { }); describe("mouseout", function () { - it("removes the 'hover' class from all elements", function () { + it("removes the .hover class from all elements", function () { container.append('span').attr('class', 'hover'); container.call(iD.behavior.Hover());