diff --git a/css/app.css b/css/app.css index 74874394e..8dfce18b7 100644 --- a/css/app.css +++ b/css/app.css @@ -3167,9 +3167,7 @@ img.tile-removing { } .radial-menu-background { - fill: none; - stroke: black; - stroke-opacity: 0.5; + fill: black; } .radial-menu-item circle { diff --git a/modules/behavior/select.js b/modules/behavior/select.js index 9c627701d..2b3628b4f 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -23,6 +23,11 @@ export function behaviorSelect(context) { function click() { + + if (d3.event.type === 'contextmenu') { + d3.event.preventDefault(); + } + var datum = d3.event.target.__data__, lasso = d3.select('#surface .lasso').node(), mode = context.mode(); @@ -56,6 +61,7 @@ export function behaviorSelect(context) { .on('keyup.select', keyup); selection.on('click.select', click); + selection.on('contextmenu.select', click); keydown(); }; diff --git a/modules/modes/select.js b/modules/modes/select.js index 27032262e..bb5c7e423 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -28,7 +28,7 @@ import { import { modeBrowse } from './browse'; import { modeDragNode } from './drag_node'; import * as Operations from '../operations/index'; -import { uiRadialMenu, uiSelectionList } from '../ui/index'; +import { uiEditMenu, uiSelectionList } from '../ui/index'; import { uiCmd } from '../ui/cmd'; import { utilEntityOrMemberSelector, utilEntitySelector } from '../util/index'; @@ -54,7 +54,7 @@ export function modeSelect(context, selectedIDs) { modeDragNode(context).selectedIDs(selectedIDs).behavior ], inspector, - radialMenu, + editMenu, newFeature = false, suppressMenu = false, follow = false; @@ -139,36 +139,30 @@ export function modeSelect(context, selectedIDs) { function closeMenu() { - if (radialMenu) { - context.surface().call(radialMenu.close); + if (editMenu) { + context.surface().call(editMenu.close); } } function positionMenu() { - if (suppressMenu || !radialMenu) { return; } + if (!editMenu) { return; } + var point = context.mouse(), + viewport = geoExtent(context.projection.clipExtent()).polygon(), + offset = (viewport[1][1] - 30) - point[1]; // 30 to account for the infoblock - var entity = singular(); - if (entity && context.geometry(entity.id) === 'relation') { - suppressMenu = true; - } else if (entity && entity.type === 'node') { - radialMenu.center(context.projection(entity.loc)); - } else { - var point = context.mouse(), - viewport = geoExtent(context.projection.clipExtent()).polygon(); - if (geoPointInPolygon(point, viewport)) { - radialMenu.center(point); - } else { - suppressMenu = true; - } + if (geoPointInPolygon(point, viewport)) { + editMenu + .center(point) + .offset(offset); } } function showMenu() { closeMenu(); - if (!suppressMenu && radialMenu) { - context.surface().call(radialMenu); + if (editMenu) { + context.surface().call(editMenu); } } @@ -196,7 +190,9 @@ export function modeSelect(context, selectedIDs) { } positionMenu(); - showMenu(); + if (d3.event && d3.event.type === 'contextmenu') { + showMenu(); + } }; @@ -425,7 +421,7 @@ export function modeSelect(context, selectedIDs) { d3.select(document) .call(keybinding); - radialMenu = uiRadialMenu(context, operations); + editMenu = uiEditMenu(context, operations); context.ui().sidebar .select(singular() ? singular().id : null, newFeature); @@ -440,8 +436,9 @@ export function modeSelect(context, selectedIDs) { selectElements(); - var show = d3.event && !suppressMenu; - + var show = d3.event; + var rtClick = d3.event && d3.event.type === 'contextmenu'; + if (show) { positionMenu(); } @@ -459,7 +456,7 @@ export function modeSelect(context, selectedIDs) { } timeout = window.setTimeout(function() { - if (show) { + if (rtClick) { showMenu(); } @@ -485,7 +482,7 @@ export function modeSelect(context, selectedIDs) { keybinding.off(); closeMenu(); - radialMenu = undefined; + editMenu = undefined; context.history() .on('undone.select', null) diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js new file mode 100644 index 000000000..a0ef6edb6 --- /dev/null +++ b/modules/ui/edit_menu.js @@ -0,0 +1,150 @@ +import * as d3 from 'd3'; +import { geoRoundCoords } from '../geo/index'; +import { uiTooltipHtml } from './tooltipHtml'; + + +export function uiEditMenu(context, operations) { + var rect, + menu, + center = [0, 0], + offset = 0, + tooltip; + + var p = 5, + l = 10, // left padding + a = 30, + a1 = (operations.length) * (a + p) + p; + + var editMenu = function(selection) { + if (!operations.length) return; + + selection.node().parentNode.focus(); + + function click(operation) { + d3.event.stopPropagation(); + if (operation.disabled()) return; + operation(); + editMenu.close(); + } + + menu = selection + .append('g') + .attr('class', 'radial-menu') + .attr('transform', 'translate(' + [center[0] + l, center[1]] + ')') + .attr('opacity', 0); + + menu + .transition() + .attr('opacity', 1); + + rect = menu + .append('g') + .attr('class', 'radial-menu-rectangle') + .attr('transform', function() { + var pos = [0, 0]; + if (offset <= a1) { + pos = [0, offset - a1]; + } + return 'translate(' + pos + ')'; + }); + + menu + .append('path') + .attr('class', 'radial-menu-background') + .attr('transform', 'translate(1, 1)') + .attr('d', 'M0 8 L8 14 L8 8 L8 2 Z'); + + rect + .append('rect') + .attr('class', 'radial-menu-background') + .attr('x', 8) + .attr('rx', 4) + .attr('ry', 4) + .attr('width', 44) + .attr('height', a1) + .attr('stroke-linecap', 'round'); + + + var button = rect.selectAll() + .data(operations) + .enter() + .append('g') + .attr('class', function(d) { return 'radial-menu-item radial-menu-item-' + d.id; }) + .classed('disabled', function(d) { return d.disabled(); }) + .attr('transform', function(d, i) { + return 'translate(' +geoRoundCoords([ + a/2 + l + p, + a/2 + p * (i + 1) + i * a]).join(',') + ')'; + }); + + button + .append('circle') + .attr('r', 15) + .on('click', click) + .on('mousedown', mousedown) + .on('mouseover', mouseover) + .on('mouseout', mouseout); + + button + .append('use') + .attr('transform', 'translate(-10,-10)') + .attr('width', '20') + .attr('height', '20') + .attr('xlink:href', function(d) { return '#operation-' + d.id; }); + + tooltip = d3.select(document.body) + .append('div') + .attr('class', 'tooltip-inner radial-menu-tooltip'); + + function mousedown() { + d3.event.stopPropagation(); // https://github.com/openstreetmap/iD/issues/1869 + } + + function mouseover(d, i) { + var rect = context.surfaceRect(), + pos = [center[0], offset <= a1 ? center[1] - (a1 - offset) : center[1]], + top = rect.top + i * (p + a)+ pos[1] + 'px', + left = rect.left + (65) + pos[0] + 'px'; + + tooltip + .style('top', top) + .style('left', left) + .style('display', 'block') + .html(uiTooltipHtml(d.tooltip(), d.keys[0])); + } + + function mouseout() { + tooltip.style('display', 'none'); + } + }; + + + editMenu.close = function() { + if (menu) { + menu + .style('pointer-events', 'none') + .transition() + .attr('opacity', 0) + .remove(); + } + + if (tooltip) { + tooltip.remove(); + } + }; + + + editMenu.center = function(_) { + if (!arguments.length) return center; + center = _; + return editMenu; + }; + + editMenu.offset = function(_) { + if (!arguments.length) return offset; + offset = _; + return editMenu; + }; + + return editMenu; +} diff --git a/modules/ui/index.js b/modules/ui/index.js index 37bfc22cc..1c8f12898 100644 --- a/modules/ui/index.js +++ b/modules/ui/index.js @@ -47,3 +47,5 @@ export { uiTooltipHtml } from './tooltipHtml'; export { uiUndoRedo } from './undo_redo'; export { uiViewOnOSM } from './view_on_osm'; export { uiZoom } from './zoom'; + +export { uiEditMenu } from './edit_menu';