From cf362cdcac99f6af70aed66dd7df04eadfccd62d Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Tue, 30 Jun 2020 11:50:56 -0400 Subject: [PATCH] Keep the edit menu open while panning the map but not zooming --- modules/renderer/map.js | 1 - modules/ui/edit_menu.js | 146 +++++++++++++++++++++++++++------------- modules/ui/popover.js | 10 ++- 3 files changed, 105 insertions(+), 52 deletions(-) diff --git a/modules/renderer/map.js b/modules/renderer/map.js index 7ed26a2cb..273f17812 100644 --- a/modules/renderer/map.js +++ b/modules/renderer/map.js @@ -641,7 +641,6 @@ export function rendererMap(context) { function resetTransform() { if (!_isTransformed) return false; - supersurface.selectAll('.edit-menu').interrupt().remove(); utilSetTransform(supersurface, 0, 0); _isTransformed = false; if (context.inIntro()) { diff --git a/modules/ui/edit_menu.js b/modules/ui/edit_menu.js index d436d64b2..94741bfe6 100644 --- a/modules/ui/edit_menu.js +++ b/modules/ui/edit_menu.js @@ -11,6 +11,7 @@ export function uiEditMenu(context) { var _operations = []; // the position the menu should be displayed relative to var _anchorLoc = [0, 0]; + var _anchorLocLonLat = [0, 0]; // a string indicating how the menu was opened var _triggerType = ''; @@ -18,6 +19,10 @@ export function uiEditMenu(context) { var _vpBottomMargin = 45; // viewport bottom margin var _vpSideMargin = 35; // viewport side margin + var _menuTop = false; + var _menuHeight; + var _menuWidth; + // hardcode these values to make menu positioning easier var _verticalPadding = 4; @@ -27,6 +32,8 @@ export function uiEditMenu(context) { // offset the menu slightly from the target location var _menuSideMargin = 10; + var _tooltips = []; + var editMenu = function(selection) { var isTouchMenu = _triggerType.includes('touch') || _triggerType.includes('pen'); @@ -37,59 +44,32 @@ export function uiEditMenu(context) { if (!ops.length) return; - var offset = [0, 0]; - var viewport = context.surfaceRect(); + _tooltips = []; // Position the menu above the anchor for stylus and finger input // since the mapper's hand likely obscures the screen below the anchor - var menuTop = isTouchMenu; + _menuTop = isTouchMenu; // Show labels for touch input since there aren't hover tooltips var showLabels = isTouchMenu; var buttonHeight = showLabels ? 32 : 34; - var menuWidth; if (showLabels) { // Get a general idea of the width based on the length of the label - menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function(op) { + _menuWidth = 52 + Math.min(120, 6 * Math.max.apply(Math, ops.map(function(op) { return op.title.length; }))); } else { - menuWidth = 44; + _menuWidth = 44; } - var menuLeft = displayOnLeft(viewport); - - offset[0] = menuLeft ? -1 * (_menuSideMargin + menuWidth) : _menuSideMargin; - - var menuHeight = _verticalPadding * 2 + ops.length * buttonHeight; - - - if (menuTop) { - if (_anchorLoc[1] - menuHeight < _vpTopMargin) { - // menu is near top viewport edge, shift downward - offset[1] = -_anchorLoc[1] + _vpTopMargin; - } else { - offset[1] = -menuHeight; - } - } else { - if (_anchorLoc[1] + menuHeight > (viewport.height - _vpBottomMargin)) { - // menu is near bottom viewport edge, shift upwards - offset[1] = -_anchorLoc[1] - menuHeight + viewport.height - _vpBottomMargin; - } else { - offset[1] = 0; - } - } - - var origin = geoVecAdd(_anchorLoc, offset); + _menuHeight = _verticalPadding * 2 + ops.length * buttonHeight; _menu = selection .append('div') .attr('class', 'edit-menu') .classed('touch-menu', isTouchMenu) - .style('padding', _verticalPadding + 'px 0') - .style('left', origin[0] + 'px') - .style('top', origin[1] + 'px'); + .style('padding', _verticalPadding + 'px 0'); var buttons = _menu.selectAll('.edit-menu-item') .data(ops); @@ -107,16 +87,16 @@ export function uiEditMenu(context) { d3_event.stopPropagation(); }); - var tooltipSide = tooltipPosition(viewport, menuLeft); - buttonsEnter.each(function(d) { + var tooltip = uiTooltip() + .heading(d.title) + .title(d.tooltip()) + .keys([d.keys[0]]); + + _tooltips.push(tooltip); + d3_select(this) - .call(uiTooltip() - .heading(d.title) - .title(d.tooltip()) - .keys([d.keys[0]]) - .placement(tooltipSide) - ) + .call(tooltip) .append('div') .attr('class', 'icon-wrap') .call(svgIcon('#iD-operation-' + d.id, 'operation-icon')); @@ -135,6 +115,18 @@ export function uiEditMenu(context) { .merge(buttons) .classed('disabled', function(d) { return d.disabled(); }); + updatePosition(); + + var initialScale = context.projection.scale(); + context.map() + .on('move.edit-menu', function() { + if (initialScale !== context.projection.scale()) { + editMenu.close(); + } + }) + .on('drawn.edit-menu', function(info) { + if (info.full) updatePosition(); + }); var lastPointerUpType; // `pointerup` is always called before `click` @@ -169,10 +161,62 @@ export function uiEditMenu(context) { } lastPointerUpType = null; } + }; + + function updatePosition() { + + if (!_menu || _menu.empty()) return; + + var anchorLoc = context.projection(_anchorLocLonLat); + + var viewport = context.surfaceRect(); + + if (anchorLoc[0] < 0 || + anchorLoc[0] > viewport.width || + anchorLoc[1] < 0 || + anchorLoc[1] > viewport.height) { + // close the menu if it's gone offscreen + + editMenu.close(); + return; + } + + var menuLeft = displayOnLeft(viewport); + + var offset = [0, 0]; + + offset[0] = menuLeft ? -1 * (_menuSideMargin + _menuWidth) : _menuSideMargin; + + if (_menuTop) { + if (anchorLoc[1] - _menuHeight < _vpTopMargin) { + // menu is near top viewport edge, shift downward + offset[1] = -anchorLoc[1] + _vpTopMargin; + } else { + offset[1] = -_menuHeight; + } + } else { + if (anchorLoc[1] + _menuHeight > (viewport.height - _vpBottomMargin)) { + // menu is near bottom viewport edge, shift upwards + offset[1] = -anchorLoc[1] - _menuHeight + viewport.height - _vpBottomMargin; + } else { + offset[1] = 0; + } + } + + var origin = geoVecAdd(anchorLoc, offset); + + _menu + .style('left', origin[0] + 'px') + .style('top', origin[1] + 'px'); + + var tooltipSide = tooltipPosition(viewport, menuLeft); + _tooltips.forEach(function(tooltip) { + tooltip.placement(tooltipSide); + }); function displayOnLeft(viewport) { if (localizer.textDirection() === 'ltr') { - if ((_anchorLoc[0] + _menuSideMargin + menuWidth) > (viewport.width - _vpSideMargin)) { + if ((anchorLoc[0] + _menuSideMargin + _menuWidth) > (viewport.width - _vpSideMargin)) { // right menu would be too close to the right viewport edge, go left return true; } @@ -180,7 +224,7 @@ export function uiEditMenu(context) { return false; } else { // rtl - if ((_anchorLoc[0] - _menuSideMargin - menuWidth) < _vpSideMargin) { + if ((anchorLoc[0] - _menuSideMargin - _menuWidth) < _vpSideMargin) { // left menu would be too close to the left viewport edge, go right return false; } @@ -196,7 +240,7 @@ export function uiEditMenu(context) { // isn't room for right-side tooltips return 'left'; } - if ((_anchorLoc[0] + _menuSideMargin + menuWidth + _tooltipWidth) > (viewport.width - _vpSideMargin)) { + if ((anchorLoc[0] + _menuSideMargin + _menuWidth + _tooltipWidth) > (viewport.width - _vpSideMargin)) { // right tooltips would be too close to the right viewport edge, go left return 'left'; } @@ -207,7 +251,7 @@ export function uiEditMenu(context) { if (!menuLeft) { return 'right'; } - if ((_anchorLoc[0] - _menuSideMargin - menuWidth - _tooltipWidth) < _vpSideMargin) { + if ((anchorLoc[0] - _menuSideMargin - _menuWidth - _tooltipWidth) < _vpSideMargin) { // left tooltips would be too close to the left viewport edge, go right return 'right'; } @@ -215,16 +259,22 @@ export function uiEditMenu(context) { return 'left'; } } - }; + } editMenu.close = function () { - _menu - .remove(); + + context.map() + .on('move.edit-menu', null) + .on('drawn.edit-menu', null); + + _menu.remove(); + _tooltips = []; }; editMenu.anchorLoc = function(val) { if (!arguments.length) return _anchorLoc; _anchorLoc = val; + _anchorLocLonLat = context.projection.invert(_anchorLoc); return editMenu; }; diff --git a/modules/ui/popover.js b/modules/ui/popover.js index 0f8a8f634..bf5915570 100644 --- a/modules/ui/popover.js +++ b/modules/ui/popover.js @@ -146,9 +146,6 @@ export function uiPopover(klass) { popoverSelection.classed('fade', true); } - var placement = _placement.apply(this, arguments); - popoverSelection.classed(placement, true); - var display = _displayType.apply(this, arguments); if (display === 'hover') { @@ -251,6 +248,13 @@ export function uiPopover(klass) { var scrollTop = scrollNode ? scrollNode.scrollTop : 0; var placement = _placement.apply(this, arguments); + popoverSelection + .classed('left', false) + .classed('right', false) + .classed('top', false) + .classed('bottom', false) + .classed(placement, true); + var alignment = _alignment.apply(this, arguments); var alignFactor = 0.5; if (alignment === 'leading') {