From d6d1709e7b40211fa16deda9a23afcca257e2477 Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Wed, 23 Jan 2013 15:30:19 -0500 Subject: [PATCH] Trying out radial menus for operations UI (#226) --- css/app.css | 22 ++++++++++ index.html | 1 + js/id/modes/select.js | 47 +++++++++------------ js/id/ui/arc_menu.js | 97 +++++++++++++++++++++++++++++++++++++++++++ js/id/ui/inspector.js | 30 +------------ test/index.html | 1 + 6 files changed, 141 insertions(+), 57 deletions(-) create mode 100644 js/id/ui/arc_menu.js diff --git a/css/app.css b/css/app.css index 7227e6d82..b520dd1a0 100644 --- a/css/app.css +++ b/css/app.css @@ -1102,6 +1102,28 @@ div.typeahead a:first-child { border-radius: 4px; } +.arc-menu-item { + fill: white; + stroke: black; + stroke-width: 1; + cursor:url(../img/cursor-pointer.png) 6 1, auto; +} + +.arc-menu-item:hover { + fill: #bde5aa; +} + +.arc-menu-item:active { + fill: #6bc641; +} + +.arc-menu-item.disabled { + cursor: auto; + pointer-events: none; + fill: rgba(255,255,255,.5); +} + + /* Media Queries ------------------------------------------------------- */ diff --git a/index.html b/index.html index 69f83f76b..7d98b97ea 100644 --- a/index.html +++ b/index.html @@ -52,6 +52,7 @@ + diff --git a/js/id/modes/select.js b/js/id/modes/select.js index d80d6fffd..d44d3d889 100644 --- a/js/id/modes/select.js +++ b/js/id/modes/select.js @@ -7,7 +7,8 @@ iD.modes.Select = function(entity, initial) { var inspector = iD.ui.inspector().initial(!!initial), keybinding = d3.keybinding('select'), - behaviors; + behaviors, + arcMenu; function remove() { if (entity.type === 'way') { @@ -32,7 +33,8 @@ iD.modes.Select = function(entity, initial) { } mode.enter = function() { - var surface = mode.map.surface; + var surface = mode.map.surface, + history = mode.history; behaviors = [ iD.behavior.Hover(), @@ -71,32 +73,12 @@ iD.modes.Select = function(entity, initial) { inspector .on('changeTags', changeTags) - .on('reverseWay', function(d) { - mode.history.perform( - iD.actions.ReverseWay(d.id), - 'reversed a way'); - - }).on('splitWay', function(d) { - mode.history.perform( - iD.actions.SplitWay(d.id), - 'split a way'); - - }).on('unjoin', function(d) { - mode.history.perform( - iD.actions.UnjoinNode(d.id), - 'unjoined ways'); - - }).on('remove', function() { - remove(); - - }).on('close', function() { - mode.controller.exit(); - }); + .on('close', function() { mode.controller.exit(); }); // Exit mode if selected entity gets undone - mode.history.on('change.entity-undone', function() { + history.on('change.entity-undone', function() { var old = entity; - entity = mode.history.graph().entity(entity.id); + entity = history.graph().entity(entity.id); if (!entity) { mode.controller.enter(iD.modes.Browse()); } else if(!_.isEqual(entity.tags, old.tags)) { @@ -121,7 +103,7 @@ iD.modes.Select = function(entity, initial) { d3.mouse(mode.map.surface.node()), mode.map), node = iD.Node({ loc: choice.loc }); - mode.history.perform( + history.perform( iD.actions.AddNode(node), iD.actions.AddWayNode(datum.id, node.id, choice.index), 'added a point to a road'); @@ -144,10 +126,17 @@ iD.modes.Select = function(entity, initial) { return d && entity && d.id === entity.id; }) .classed('selected', true); + + arcMenu = iD.ui.ArcMenu(entity, history); + + if (d3.event) { + surface.call(arcMenu, d3.mouse(surface.node())); + } }; mode.exit = function () { - var surface = mode.map.surface; + var surface = mode.map.surface, + history = mode.history; if (entity) { changeTags(entity, inspector.tags()); @@ -173,10 +162,12 @@ iD.modes.Select = function(entity, initial) { surface.on('click.select', null) .on('dblclick.select', null); - mode.history.on('change.entity-undone', null); + history.on('change.entity-undone', null); surface.selectAll(".selected") .classed('selected', false); + + surface.call(arcMenu.close); }; return mode; diff --git a/js/id/ui/arc_menu.js b/js/id/ui/arc_menu.js new file mode 100644 index 000000000..09b0ec1d5 --- /dev/null +++ b/js/id/ui/arc_menu.js @@ -0,0 +1,97 @@ +iD.ui.ArcMenu = function(entity, history) { + var arcMenu = function(selection, center) { + var π = Math.PI; + var operations; + + if (entity.geometry() === 'vertex') { + operations = [ + { + id: 'delete', + text: 'Delete', + description: 'deleted a node', + action: iD.actions.DeleteNode(entity.id) + }, + { + id: 'split', + text: 'Split Way', + description: 'split a way', + action: iD.actions.SplitWay(entity.id) + }, + { + id: 'unjoin', + text: 'Unjoin', + description: 'unjoined lines', + action: iD.actions.UnjoinNode(entity.id) + } + ]; + } else if (entity.geometry() === 'point') { + operations = [ + { + id: 'delete', + text: 'Delete', + description: 'deleted a point', + action: iD.actions.DeleteNode(entity.id) + } + ]; + } else if (entity.geometry() === 'line') { + operations = [ + { + id: 'delete', + text: 'Delete', + description: 'deleted a line', + action: iD.actions.DeleteWay(entity.id) + }, + { + id: 'reverse', + text: 'Reverse', + description: 'reversed a way', + action: iD.actions.ReverseWay(entity.id) + } + ]; + } else if (entity.geometry() === 'area') { + operations = [ + { + id: 'delete', + text: 'Delete', + description: 'deleted an area', + action: iD.actions.DeleteWay(entity.id) + } + ]; + } + + var arc = d3.svg.arc() + .outerRadius(70) + .innerRadius(30) + .startAngle(function (d, i) { return 2 * Math.PI / operations.length * i; }) + .endAngle(function (d, i) { return 2 * Math.PI / operations.length * (i + 1); }); + + var arcs = selection.selectAll('.arc-menu') + .data(operations) + .enter().append('g') + .attr('class', 'arc-menu') + .attr('transform', "translate(" + center + ")") + .attr('opacity', 0); + + arcs.transition() + .attr('opacity', 0.8); + + arcs.append('path') + .attr('class', function (d) { return 'arc-menu-item arc-menu-item-' + d.id; }) + .attr('d', arc) + .classed('disabled', function (d) { return !d.action.enabled(history.graph()); }) + .on('click', function (d) { history.perform(d.action, d.description); }); + + arcs.append('text') + .attr("transform", function(d, i) { return "translate(" + arc.centroid(d, i) + ")"; }) + .attr("dy", ".35em") + .style("text-anchor", "middle") + .text(function(d) { return d.text; }); + }; + + arcMenu.close = function(selection) { + selection.selectAll('.arc-menu') + .remove(); + }; + + return arcMenu; +}; diff --git a/js/id/ui/inspector.js b/js/id/ui/inspector.js index 22fac522d..b784e5fb3 100644 --- a/js/id/ui/inspector.js +++ b/js/id/ui/inspector.js @@ -1,6 +1,5 @@ iD.ui.inspector = function() { - var event = d3.dispatch('changeTags', 'reverseWay', - 'update', 'remove', 'close', 'splitWay', 'unjoin'), + var event = d3.dispatch('changeTags', 'close'), taginfo = iD.taginfo(), initial = false, tagList; @@ -69,39 +68,12 @@ iD.ui.inspector = function() { inspectorButton1.append('span').attr('class','icon icon-pre-text apply'); inspectorButton1.append('span').attr('class','label').text('Okay'); - var inspectorButton2 = inspectorButtonWrap.append('button') - .attr('class', 'delete col6 action') - .on('click', function(entity) { event.remove(entity); }); - - inspectorButton2.append('span').attr('class','icon icon-pre-text delete'); - inspectorButton2.append('span').attr('class','label').text('Delete'); - var minorButtons = selection.append('div').attr('class','minor-buttons fl'); minorButtons.append('a') .attr('href', 'http://www.openstreetmap.org/browse/' + entity.type + '/' + entity.osmId()) .attr('target', '_blank') .text('View on OSM'); - - if (entity.type === 'way') { - minorButtons.append('a') - .attr('href', '#') - .text('Reverse Direction') - .on('click', function() { event.reverseWay(entity); }); - } - - if (entity.geometry() === 'vertex') { - minorButtons.append('a') - .attr('href', '#') - .text('Split Way') - .on('click', function() { event.splitWay(entity); }); - - minorButtons.append('a') - .attr('href', '#') - .text('Unjoin') - .on('click', function() { event.unjoin(entity); }); - } - } function drawTags(tags) { diff --git a/test/index.html b/test/index.html index e7e6701a6..aea53c1e9 100644 --- a/test/index.html +++ b/test/index.html @@ -54,6 +54,7 @@ +