-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Provides single API for both raw and gestural events; leverages hammer.js for both. Replaces use of luma.gl's addEvents. Does not yet reimplement support for dragging (i.e. dragging is broken as of this commit).
- Loading branch information
Showing
7 changed files
with
1,089 additions
and
567 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import {createGestureManager, EVENT_RECOGNIZER_MAP} from './gesture-manager'; | ||
|
||
const EVENT_HANDLER_MAP = { | ||
pointerdown: 'mousedown', | ||
pointermove: 'mousemove', | ||
pointerup: 'mouseup', | ||
mousedown: 'mousedown', | ||
mousemove: 'mousemove', | ||
mouseup: 'mouseup' | ||
}; | ||
const GESTURE_ALIASES = { | ||
click: 'tap' | ||
}; | ||
|
||
const DEFAULT_OPTIONS = { | ||
handleRawEventsWithGestureManager: true | ||
}; | ||
|
||
/** | ||
* Single API for subscribing to events about both "raw" input (e.g. 'mousemove', 'click') | ||
* and gestural input (e.g. 'panstart', 'tap'). | ||
*/ | ||
class EventManager { | ||
constructor(element, options) { | ||
this.element = element; | ||
this.handlers = {}; | ||
this.gestureManager = createGestureManager(element, options); | ||
|
||
// If specified, use GestureManager for raw event handling | ||
// as well as for gesture recognition. | ||
if (options.handleRawEventsWithGestureManager) { | ||
this._onRawInput = this._onRawInput.bind(this); | ||
this.gestureManager.startHandlingRawEvents(this._onRawInput); | ||
} | ||
} | ||
|
||
on(event, handler) { | ||
if (typeof event === 'string') { | ||
// wrap and store handler | ||
if (!this.handlers[event]) { | ||
this.handlers[event] = []; | ||
} | ||
const handlersForEvent = this.handlers[event]; | ||
let handlerForEvent = handlersForEvent.find(h => h.handler === handler); | ||
if (!handlerForEvent) { | ||
handlerForEvent = { | ||
handler, | ||
wrapper: e => handler(this._wrapEvent(e, 'srcEvent')) | ||
}; | ||
handlersForEvent.push(handlerForEvent); | ||
} | ||
|
||
// Handle as a gestural event if appropriate | ||
const aliasedEvent = GESTURE_ALIASES[event] || event; | ||
if (EVENT_RECOGNIZER_MAP[aliasedEvent]) { | ||
this.gestureManager.on(aliasedEvent, handlerForEvent.wrapper); | ||
} | ||
} else { | ||
for (const eventName in event) { | ||
this.on(eventName, event[eventName]); | ||
} | ||
} | ||
} | ||
|
||
off(event, handler) { | ||
if (typeof event === 'string') { | ||
// find wrapped handler | ||
const handlersForEvent = this.handlers[event]; | ||
if (handlersForEvent) { | ||
const i = handlersForEvent.findIndex(h => h.handler === handler); | ||
let wrapper; | ||
if (i !== -1) { | ||
wrapper = handlersForEvent.splice(i, 1)[0].wrapper; | ||
} | ||
if (!handlersForEvent.length) { | ||
delete this.handlers[event]; | ||
} | ||
|
||
// Handle as a gestural event if appropriate | ||
const aliasedEvent = GESTURE_ALIASES[event] || event; | ||
if (wrapper && EVENT_RECOGNIZER_MAP[aliasedEvent]) { | ||
this.gestureManager.off(aliasedEvent, wrapper); | ||
} | ||
} | ||
} else { | ||
for (const eventName in event) { | ||
this.off(eventName, event[eventName]); | ||
} | ||
} | ||
} | ||
|
||
_onRawInput(event) { | ||
if (EVENT_RECOGNIZER_MAP[event.type]) { | ||
// let GestureManager handle these events | ||
return; | ||
} | ||
|
||
const {srcEvent} = event; | ||
const normalizedEventType = srcEvent && EVENT_HANDLER_MAP[srcEvent.type]; | ||
if (!normalizedEventType) { | ||
// not a recognized event type | ||
return; | ||
} | ||
|
||
const handlersForEvent = this.handlers[normalizedEventType]; | ||
if (handlersForEvent) { | ||
handlersForEvent.forEach(handler => handler.wrapper(event)); | ||
} | ||
} | ||
|
||
_wrapEvent(event, srcEventPropName) { | ||
// TODO: decide on how best to wrap for cross-browser compatibility | ||
// possible options: | ||
// - luma.gl's `EventsProxy.eventInfo()` wrapper | ||
// (https://github.com/uber/luma.gl/blob/master/src/addons/event.js#L171) | ||
// - npm `synthetic-dom-event` | ||
// (https://www.npmjs.com/package/synthetic-dom-events)) | ||
// - hammer.js `compute-input-data` wrapper + additional info | ||
// (https://github.com/hammerjs/hammer.js/blob/master/src/inputjs/compute-input-data.js) | ||
|
||
// TODO: consider refactoring deck.gl to look for `srcEvent` instead of `event` | ||
// for original/source event object; | ||
// `event.srcEvent` is more descriptive than `event.event` | ||
|
||
return { | ||
event: event[srcEventPropName] | ||
}; | ||
} | ||
} | ||
|
||
export function createEventManager(element, options = {}) { | ||
// TODO: should this be a Singleton? | ||
// conversely, is actually a good reason to use the Factory pattern here, | ||
// or should we allow direct instantiation? | ||
options = Object.assign({}, DEFAULT_OPTIONS, options); | ||
return new EventManager(element, options); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { | ||
Manager, | ||
Tap, | ||
Press, | ||
Pinch, | ||
Rotate, | ||
Pan, | ||
Swipe | ||
} from 'hammerjs'; | ||
import WheelInput from './wheel-input'; | ||
import log from '../lib/utils/log'; | ||
|
||
export const EVENT_RECOGNIZER_MAP = { | ||
tap: 'tap', | ||
doubletap: 'tap', | ||
press: 'press', | ||
pinch: 'pinch', | ||
pinchin: 'pinch', | ||
pinchout: 'pinch', | ||
pinchstart: 'pinch', | ||
pinchmove: 'pinch', | ||
pinchend: 'pinch', | ||
pinchcancel: 'pinch', | ||
rotate: 'rotate', | ||
rotatestart: 'rotate', | ||
rotatemove: 'rotate', | ||
rotateend: 'rotate', | ||
rotatecancel: 'rotate', | ||
pan: 'pan', | ||
panstart: 'pan', | ||
panmove: 'pan', | ||
panup: 'pan', | ||
pandown: 'pan', | ||
panleft: 'pan', | ||
panright: 'pan', | ||
panend: 'pan', | ||
pancancel: 'pan', | ||
swipe: 'swipe', | ||
swipeleft: 'swipe', | ||
swiperight: 'swipe', | ||
swipeup: 'swipe', | ||
swipedown: 'swipe' | ||
}; | ||
|
||
const RECOGNIZERS = { | ||
doubletap: new Tap({ | ||
event: 'doubletap', | ||
taps: 2 | ||
}), | ||
pan: new Pan({ | ||
threshold: 10 | ||
}), | ||
pinch: new Pinch(), | ||
press: new Press(), | ||
rotate: new Rotate(), | ||
swipe: new Swipe(), | ||
tap: new Tap() | ||
}; | ||
|
||
class GestureManager { | ||
|
||
constructor(element, options) { | ||
// TODO: support overriding default RECOGNIZERS by passing | ||
// recognizers / configs, keyed to event name. | ||
|
||
// how to get inputClass from createInputInstance without a Manager instance? | ||
// mostly just need to run logic from createInputInstance... | ||
// Issue filed: https://github.com/hammerjs/hammer.js/issues/1106 | ||
const inputClass = WheelInput; | ||
|
||
this.manager = new Manager(element, { | ||
inputClass | ||
}); | ||
} | ||
|
||
on(event, handler) { | ||
const recognizerEvent = this._getRecognizerEvent(event); | ||
if (!recognizerEvent) { | ||
return; | ||
} | ||
|
||
// add recognizer for this event if not already added. | ||
if (!this.manager.get(recognizerEvent)) { | ||
this.manager.add(RECOGNIZERS[recognizerEvent]); | ||
} | ||
|
||
// TODO: verify event+handler not already registered. | ||
// https://github.com/hammerjs/hammer.js/issues/1107 | ||
this.manager.on(event, handler); | ||
} | ||
|
||
off(event, handler) { | ||
const recognizerEvent = this._getRecognizerEvent(event); | ||
if (!recognizerEvent) { | ||
return; | ||
} | ||
|
||
this.manager.off(event, handler); | ||
} | ||
|
||
startHandlingRawEvents(handler) { | ||
this.manager.on('hammer.input', handler); | ||
} | ||
|
||
stopHandlingRawEvents(handler) { | ||
this.manager.off('hammer.input', handler); | ||
} | ||
|
||
_getRecognizerEvent(event) { | ||
const recognizerEvent = EVENT_RECOGNIZER_MAP[event]; | ||
if (!recognizerEvent) { | ||
log(1, 'no gesture recognizer available for event', event); | ||
} | ||
return recognizerEvent; | ||
} | ||
} | ||
|
||
export function createGestureManager(element, options = {}) { | ||
return new GestureManager(element, options); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import {PointerEventInput} from 'hammerjs'; | ||
|
||
const POINTER_MOVE = 'pointermove'; | ||
|
||
// Copied from Hammer.js' pointerevent.js | ||
const IE10_POINTER_TYPE_ENUM = { | ||
2: 'touch', | ||
3: 'pen', | ||
4: 'mouse', | ||
5: 'kinect' // see https://twitter.com/jacobrossi/status/480596438489890816 | ||
}; | ||
|
||
export default class PointerMoveEventInput extends PointerEventInput { | ||
|
||
constructor(...opts) { | ||
super(...opts); | ||
} | ||
|
||
handler(event) { | ||
// let 'pointermove' events through when the pointer is not down. | ||
// all other cases (including 'pointermove' while pointer is down) | ||
// are handled by PointerEventInput. | ||
const {store} = this; | ||
if (event.type === POINTER_MOVE) { | ||
const storeIndex = store.findIndex(i => | ||
i.pointerId == event.pointerId); // eslint-disable-line eqeqeq | ||
if (storeIndex < 0) { | ||
this.callback(this.manager, POINTER_MOVE, { | ||
pointers: store, | ||
changedPointers: [event], | ||
pointerType: IE10_POINTER_TYPE_ENUM[event.pointerType] || event.pointerType, | ||
srcEvent: event | ||
}); | ||
return; | ||
} | ||
} | ||
|
||
super.handler(event); | ||
} | ||
} |
Oops, something went wrong.