diff --git a/src/event.js b/src/event.js index 6cfd895331..814afbd848 100644 --- a/src/event.js +++ b/src/event.js @@ -163,6 +163,28 @@ geo_event.mousemove = 'geo_mousemove'; */ geo_event.mouseclick = 'geo_mouseclick'; +/** + * Triggered on `mouseup` events. The event object extends + * {@link geo.mouseState}. + * + * @event geo.event.mouseup + * @type {(geo.event.base|geo.mouseState)} + * @property {geo.mouseButtons} buttonsDown The buttons that were down at the + * start of the up action. + */ +geo_event.mouseup = 'geo_mouseup'; + +/** + * Triggered on `mousedown` events. The event object extends + * {@link geo.mouseState}. + * + * @event geo.event.mousedown + * @type {(geo.event.base|geo.mouseState)} + * @property {geo.mouseButtons} buttonsDown The buttons that were down at the + * end of the down action. + */ +geo_event.mousedown = 'geo_mousedown'; + /** * Triggered on every `mousemove` during a brushing selection. * The event object extends {@link geo.brushSelection}. @@ -485,6 +507,41 @@ geo_event.feature = { * @property {geo.event} sourceEvent The underlying event that trigger this. */ mouseclick_order: 'geo_feature_mouseclick_order', + /** + * The event is the feature version of {@link geo.event.mousedown}. + * + * @event geo.event.feature.mousedown + * @type {geo.event.base} + * @property {object} data The feature data the mouse is above. + * @property {number} index The index of the feature data the mouse is above. + * @property {object} extra Extra information about the feature and mouse + * location. + * @property {geo.mouseState} mouse The mouse state. + * @property {number} eventID a monotonically increasing event number. All + * features that the mouse goes down on simultaneously will have the same + * `eventID`. + * @property {boolean} top True if this is the topmost data element. + * @property {geo.event} sourceEvent The underlying event that trigger this. + */ + mousedown: 'geo_feature_mousedown', + /** + * The event is the feature version of {@link geo.event.mouseup}. + * + * @event geo.event.feature.mouseup + * @type {geo.event.base} + * @property {object} data The feature data the mouse is above. + * @property {number} index The index of the feature data the mouse is above. + * @property {object} extra Extra information about the feature and mouse + * location. + * @property {geo.mouseState} mouse The mouse state. The buttons are before + * the up action occurs. + * @property {number} eventID a monotonically increasing event number. All + * features that the mouse goes up on simultaneously will have the same + * `eventID`. + * @property {boolean} top True if this is the topmost data element. + * @property {geo.event} sourceEvent The underlying event that trigger this. + */ + mouseup: 'geo_feature_mouseup', /** * This event is fired for each data component of a feature under a brush * that has just finished its selection. diff --git a/src/feature.js b/src/feature.js index 4c7b17d463..b9f9212442 100644 --- a/src/feature.js +++ b/src/feature.js @@ -162,6 +162,8 @@ var feature = function (arg) { m_this._unbindMouseHandlers(); m_this.geoOn(geo_event.mousemove, m_this._handleMousemove); + m_this.geoOn(geo_event.mousedown, m_this._handleMousedown); + m_this.geoOn(geo_event.mouseup, m_this._handleMouseup); m_this.geoOn(geo_event.mouseclick, m_this._handleMouseclick); m_this.geoOn(geo_event.brushend, m_this._handleBrushend); m_this.geoOn(geo_event.brush, m_this._handleBrush); @@ -172,6 +174,8 @@ var feature = function (arg) { */ this._unbindMouseHandlers = function () { m_this.geoOff(geo_event.mousemove, m_this._handleMousemove); + m_this.geoOff(geo_event.mousedown, m_this._handleMousedown); + m_this.geoOff(geo_event.mouseup, m_this._handleMouseup); m_this.geoOff(geo_event.mouseclick, m_this._handleMouseclick); m_this.geoOff(geo_event.brushend, m_this._handleBrushend); m_this.geoOff(geo_event.brush, m_this._handleBrush); @@ -238,20 +242,46 @@ var feature = function (arg) { }; }; + /** + * Private mousedown handler. This uses `pointSearch` to determine which + * features the mouse is over, then fires appropriate events. + * + * @param {geo.event} evt The event that triggered this handler. + * @fires geo.event.feature.mousedown + */ + this._handleMousedown = function (evt) { + this._handleMousemove(evt, geo_event.feature.mousedown); + }; + + /** + * Private mouseup handler. This uses `pointSearch` to determine which + * features the mouse is over, then fires appropriate events. + * + * @param {geo.event} evt The event that triggered this handler. + * @fires geo.event.feature.mouseup + */ + this._handleMouseup = function (evt) { + this._handleMousemove(evt, geo_event.feature.mouseup); + }; + /** * Private mousemove handler. This uses `pointSearch` to determine which * features the mouse is over, then fires appropriate events. * * @param {geo.event} evt The event that triggered this handler. + * @param {string} [updown] If "mouseup" or "mousedown", fire that event + * instead of mouseon. * @fires geo.event.feature.mouseover_order * @fires geo.event.feature.mouseover * @fires geo.event.feature.mouseout * @fires geo.event.feature.mousemove * @fires geo.event.feature.mouseoff * @fires geo.event.feature.mouseon + * @fires geo.event.feature.mouseup + * @fires geo.event.feature.mousedown */ - this._handleMousemove = function (evt) { - var mouse = m_this.layer().map().interactor().mouse(), + this._handleMousemove = function (evt, updown) { + var mouse = evt && evt.mouse ? evt.mouse : m_this.layer().map().interactor().mouse(), data = m_this.data(), over = m_this.pointSearch(mouse.geo), newFeatures = [], oldFeatures = [], lastTop = -1, top = -1, extra; @@ -277,6 +307,23 @@ var feature = function (arg) { }); } + feature.eventID += 1; + + if (updown) { + over.index.forEach((i, idx) => { + m_this.geoTrigger(updown, { + data: data[i], + index: i, + extra: extra[i], + mouse: mouse, + eventID: feature.eventID, + top: idx === over.length - 1, + sourceEvent: evt + }, true); + }); + return; + } + // Get the index of the element that was previously on top if (m_selectedFeatures.length) { lastTop = m_selectedFeatures[m_selectedFeatures.length - 1]; @@ -290,7 +337,6 @@ var feature = function (arg) { return over.index.indexOf(i) < 0; }); - feature.eventID += 1; // Fire events for mouse in first. newFeatures.forEach(function (i, idx) { m_this.geoTrigger(geo_event.feature.mouseover, { diff --git a/src/mapInteractor.js b/src/mapInteractor.js index e20c6a5179..93542c6b7f 100644 --- a/src/mapInteractor.js +++ b/src/mapInteractor.js @@ -1090,6 +1090,7 @@ var mapInteractor = function (args) { * @param {jQuery.Event} evt The event that triggered this. * @fires geo.event.brushstart * @fires geo.event.actiondown + * @fires geo.event.mousedown */ this._handleMouseDown = function (evt) { var action, actionRecord; @@ -1109,6 +1110,9 @@ var mapInteractor = function (args) { m_this._getMouseButton(evt); m_this._getMouseModifiers(evt); + console.log(JSON.stringify(m_this.mouse().buttons)); // DWM:: + m_this.map().geoTrigger(geo_event.mousedown, m_this.mouse()); + if (m_options.click.enabled && (!m_mouse.buttons.left || m_options.click.buttons.left) && (!m_mouse.buttons.right || m_options.click.buttons.right) && @@ -1538,6 +1542,7 @@ var mapInteractor = function (args) { * @fires geo.event.brushend * @fires geo.event.actionselection * @fires geo.event.actionup + * @fires geo.event.mouseup * @fires geo.event.select * @fires geo.event.zoomselect * @fires geo.event.unzoomselect @@ -1625,6 +1630,9 @@ var mapInteractor = function (args) { if (m_clickMaybe) { m_this._handleMouseClick(evt); } + var details = m_this.mouse(); + details.buttonsDown = m_clickMaybe.buttons; + m_this.map().geoTrigger(geo_event.mouseup, details); }; /** diff --git a/tutorials/tiled-pixelmap/index.pug b/tutorials/tiled-pixelmap/index.pug index f737a3bbbb..e4b3a181a9 100644 --- a/tutorials/tiled-pixelmap/index.pug +++ b/tutorials/tiled-pixelmap/index.pug @@ -98,6 +98,11 @@ block mainTutorial } }).draw(); pixelmap.geoOn(geo.event.feature.mouseclick, function (evt) { + // ignore clicks with a shift; this prevents conflicting interaction in + // the next step + if (evt.mouse.modifiers.shift) { + return; + } // use the index of the superpixel rather than its border, regardless of // which was clicked. var index = evt.index - evt.index % 2; @@ -121,8 +126,8 @@ block mainTutorial map.interactor().removeAction(geo.geo_action.zoomselect); // if the mouse moves, decide whether we should handle it var newValue = undefined; - pixelmap.geoOn(geo.event.feature.mousemove, function (evt) { - // if shift or the left button aren't down, stop setting values + function onOver(evt) { + // for movement, if shift or the left button aren't down, stop setting values if (!evt.mouse.modifiers.shift || !evt.mouse.buttons.left) { newValue = undefined; return; @@ -131,7 +136,7 @@ block mainTutorial var index = evt.index - evt.index % 2; var data = pixelmap.data(); // if we just started a movement with shift, update the value and record it - if (newValue === undefined) { + if (newValue === undefined || evt.event === geo.event.feature.mousedown) { data[index] = data[index + 1] = (data[index] + 1) % 4; newValue = data[index]; pixelmap.indexModified(index, index + 1).draw(); @@ -141,4 +146,9 @@ block mainTutorial data[index] = data[index + 1] = newValue; pixelmap.indexModified(index, index + 1).draw(); } - }); + } + // An alternative would be to bind on mousemove instead of mousedown and + // mouseover and leave the click handler; this requires movement to start + // setting values, whereas mousedown doesn't require mouse movement. + pixelmap.geoOn(geo.event.feature.mousedown, onOver); + pixelmap.geoOn(geo.event.feature.mouseover, onOver);