From 38db23fab511d51a4b5b1bfc39579a1e87796bbe Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Wed, 27 May 2020 15:07:23 -0400 Subject: [PATCH] Extend behaviorHover to support multiple pointers --- modules/behavior/draw_way.js | 4 +- modules/behavior/hover.js | 140 ++++++++++++++++++----------------- modules/modes/drag_node.js | 8 +- modules/ui/sidebar.js | 9 ++- 4 files changed, 84 insertions(+), 77 deletions(-) diff --git a/modules/behavior/draw_way.js b/modules/behavior/draw_way.js index ceff6fd24..1c506cfb8 100644 --- a/modules/behavior/draw_way.js +++ b/modules/behavior/draw_way.js @@ -308,8 +308,8 @@ export function behaviorDrawWay(context, wayID, mode, startGraph) { .classed('nope-disabled', false); d3_select(window) - .on('keydown.hover', null) - .on('keyup.hover', null); + .on('keydown.drawWay', null) + .on('keyup.drawWay', null); context.history() .on('undone.draw', null); diff --git a/modules/behavior/hover.js b/modules/behavior/hover.js index ed22900b7..1d20f0996 100644 --- a/modules/behavior/hover.js +++ b/modules/behavior/hover.js @@ -23,10 +23,9 @@ export function behaviorHover(context) { var _selection = d3_select(null); var _newNodeId = null; var _initialNodeID = null; - var _buttonDown; var _altDisables; var _ignoreVertex; - var _target; + var _targets = []; // use pointer events on supported platforms; fallback to mouse events var _pointerPrefix = 'PointerEvent' in window ? 'pointer' : 'mouse'; @@ -55,7 +54,7 @@ export function behaviorHover(context) { _selection .classed('hover-disabled', false); - dispatch.call('hover', this, _target ? _target.id : null); + dispatch.call('hover', this, _targets); } } @@ -63,6 +62,8 @@ export function behaviorHover(context) { function behavior(selection) { _selection = selection; + _targets = []; + if (_initialNodeID) { _newNodeId = _initialNodeID; _initialNodeID = null; @@ -73,38 +74,43 @@ export function behaviorHover(context) { _selection .on(_pointerPrefix + 'over.hover', pointerover) .on(_pointerPrefix + 'out.hover', pointerout) - .on(_pointerPrefix + 'down.hover', pointerdown); + // treat pointerdown as pointerover for touch devices + .on(_pointerPrefix + 'down.hover', pointerover); d3_select(window) + .on(_pointerPrefix + 'up.hover', pointerout, true) .on('keydown.hover', keydown) .on('keyup.hover', keyup); - function pointerover() { - if (_buttonDown) return; - var target = d3_event.target; - enter(target ? target.__data__ : null); + function eventTarget() { + var datum = d3_event.target && d3_event.target.__data__; + if (typeof datum !== 'object') return null; + if (!(datum instanceof osmEntity) && datum.properties && (datum.properties.entity instanceof osmEntity)) { + return datum.properties.entity; + } + return datum; } + function pointerover() { + // ignore mouse hovers with buttons pressed + if ((!d3_event.pointerType || d3_event.pointerType === 'mouse') && d3_event.buttons) return; + + var target = eventTarget(); + if (target && _targets.indexOf(target) === -1) { + _targets.push(target); + updateHover(_targets); + } + } function pointerout() { - if (_buttonDown) return; - var target = d3_event.relatedTarget; - enter(target ? target.__data__ : null); - } - - function pointerdown() { - _buttonDown = true; - d3_select(window) - .on(_pointerPrefix + 'up.hover', pointerup, true); - } - - - function pointerup() { - _buttonDown = false; - d3_select(window) - .on(_pointerPrefix + 'up.hover', null, true); + var target = eventTarget(); + var index = _targets.indexOf(target); + if (index !== -1) { + _targets.splice(index); + updateHover(_targets); + } } function allowsVertex(d) { @@ -120,65 +126,64 @@ export function behaviorHover(context) { return true; } - function enter(datum) { - if (datum === _target) return; - _target = datum; + function updateHover(targets) { + + var mode = context.mode(); _selection.selectAll('.hover') .classed('hover', false); _selection.selectAll('.hover-suppressed') .classed('hover-suppressed', false); - // What are we hovering over? - var entity, selector; - if (datum && datum.__featurehash__) { - entity = datum; - selector = '.data' + datum.__featurehash__; + var selector = ''; - } else if (datum instanceof QAItem) { - entity = datum; - selector = '.' + datum.service + '.itemId-' + datum.id; + for (var i in targets) { + var datum = targets[i]; - } else if (datum instanceof osmNote) { - entity = datum; - selector = '.note-' + datum.id; + // What are we hovering over? + if (datum.__featurehash__) { + // hovering custom data + selector += ', .data' + datum.__featurehash__; - } else if (datum instanceof osmEntity) { - entity = datum; - selector = '.' + entity.id; - if (entity.type === 'relation') { - entity.members.forEach(function(member) { selector += ', .' + member.id; }); - } - } else if (datum && datum.properties && (datum.properties.entity instanceof osmEntity)) { - entity = datum.properties.entity; - selector = '.' + entity.id; - if (entity.type === 'relation') { - entity.members.forEach(function(member) { selector += ', .' + member.id; }); + } else if (datum instanceof QAItem) { + selector += ', .' + datum.service + '.itemId-' + datum.id; + + } else if (datum instanceof osmNote) { + selector += ', .note-' + datum.id; + + } else if (datum instanceof osmEntity) { + + // If drawing a way, don't hover on a node that was just placed. #3974 + if ((mode.id === 'draw-line' || mode.id === 'draw-area') && + !_newNodeId && + datum.type === 'node') { + + _newNodeId = datum.id; + + } else if (datum.id !== _newNodeId && + (datum.type !== 'node' || !_ignoreVertex || allowsVertex(datum)) && + modeAllowsHover(datum)) { + + selector += ', .' + datum.id; + if (datum.type === 'relation') { + for (var j in datum.members) { + selector += ', .' + datum.members[j].id; + } + } + } } } - var mode = context.mode(); + if (selector.trim().length) { + // remove the first comma + selector = selector.slice(1); - // Update hover state and dispatch event - if (entity && entity.id !== _newNodeId) { - // If drawing a way, don't hover on a node that was just placed. #3974 - - if ((mode.id === 'draw-line' || mode.id === 'draw-area') && !_newNodeId && entity.type === 'node') { - _newNodeId = entity.id; - return; - } - - var suppressed = (_altDisables && d3_event && d3_event.altKey) || - (entity.type === 'node' && _ignoreVertex && !allowsVertex(entity)) || - !modeAllowsHover(entity); + var suppressed = _altDisables && d3_event && d3_event.altKey; _selection.selectAll(selector) .classed(suppressed ? 'hover-suppressed' : 'hover', true); - - dispatch.call('hover', this, !suppressed && entity); - - } else { - dispatch.call('hover', this, null); } + + dispatch.call('hover', this, !suppressed && targets); } } @@ -197,6 +202,7 @@ export function behaviorHover(context) { .on(_pointerPrefix + 'down.hover', null); d3_select(window) + .on(_pointerPrefix + 'up.hover', null, true) .on('keydown.hover', null) .on('keyup.hover', null); }; diff --git a/modules/modes/drag_node.js b/modules/modes/drag_node.js index a2db69a21..33d81ac83 100644 --- a/modules/modes/drag_node.js +++ b/modules/modes/drag_node.js @@ -457,8 +457,8 @@ export function modeDragNode(context) { context.install(edit); d3_select(window) - .on('keydown.drawWay', keydown) - .on('keyup.drawWay', keyup); + .on('keydown.dragNode', keydown) + .on('keyup.dragNode', keyup); context.history() .on('undone.drag-node', cancel); @@ -471,8 +471,8 @@ export function modeDragNode(context) { context.uninstall(edit); d3_select(window) - .on('keydown.hover', null) - .on('keyup.hover', null); + .on('keydown.dragNode', null) + .on('keyup.dragNode', null); context.history() .on('undone.drag-node', null); diff --git a/modules/ui/sidebar.js b/modules/ui/sidebar.js index c59fa8a0e..c6b3a47c8 100644 --- a/modules/ui/sidebar.js +++ b/modules/ui/sidebar.js @@ -153,15 +153,15 @@ export function uiSidebar(context) { .append('div') .attr('class', 'inspector-hidden inspector-wrap fr'); - var hoverModeSelect = function(datum) { + var hoverModeSelect = function(targets) { context.container().selectAll('.feature-list-item').classed('hover', false); if (context.selectedIDs().length > 1 && - datum instanceof osmEntity){ + targets && targets.length) { var elements = context.container().selectAll('.feature-list-item') .filter(function (node) { - return node.id === datum.id; + return targets.indexOf(node) !== -1; }); if (!elements.empty()) { @@ -172,7 +172,8 @@ export function uiSidebar(context) { sidebar.hoverModeSelect = _throttle(hoverModeSelect, 200); - function hover(datum) { + function hover(targets) { + var datum = targets && targets.length && targets[0]; if (datum && datum.__featurehash__) { // hovering on data _wasData = true; sidebar