pointer events generated by mouse events

This commit is contained in:
tsauerwein
2014-02-03 14:37:59 +01:00
parent 1a40b1793c
commit 7140f608f8
6 changed files with 1065 additions and 208 deletions

View File

@@ -8,7 +8,8 @@ goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.object');
goog.require('ol.pointer.PointerEventHandler');
goog.require('ol.Coordinate');
goog.require('ol.MapEvent');
goog.require('ol.Pixel');
@@ -114,12 +115,6 @@ ol.MapBrowserEventHandler = function(map) {
*/
this.dragListenerKeys_ = null;
/**
* @type {goog.events.Key}
* @private
*/
this.mousedownListenerKey_ = null;
/**
* @type {goog.events.Key}
* @private
@@ -127,10 +122,10 @@ ol.MapBrowserEventHandler = function(map) {
this.pointerdownListenerKey_ = null;
/**
* @type {goog.events.Key}
* @type {goog.events.BrowserEvent}
* @private
*/
this.touchstartListenerKey_ = null;
this.down_ = null;
if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
/**
@@ -141,38 +136,23 @@ ol.MapBrowserEventHandler = function(map) {
}
/**
* @type {goog.events.BrowserEvent}
* @type {ol.pointer.PointerEventHandler}
* @private
*/
this.down_ = null;
this.PointerEventHandler_ = null;
var element = this.map_.getViewport();
this.relayedListenerKeys_ = [
goog.events.listen(element,
goog.events.EventType.MOUSEMOVE,
this.relayEvent_, false, this),
goog.events.listen(element,
goog.events.EventType.CLICK,
this.relayEvent_, false, this)
];
this.mousedownListenerKey_ = goog.events.listen(element,
goog.events.EventType.MOUSEDOWN,
this.handleMouseDown_, false, this);
this.pointerdownListenerKey_ = goog.events.listen(element,
goog.events.EventType.MSPOINTERDOWN,
this.pointerEventHandler_ = new ol.pointer.PointerEventHandler(element);
this.pointerdownListenerKey_ = goog.events.listen(this.pointerEventHandler_,
ol.pointer.EventType.POINTERDOWN,
this.handlePointerDown_, false, this);
this.touchstartListenerKey_ = goog.events.listen(element,
goog.events.EventType.TOUCHSTART,
this.handleTouchStart_, false, this);
if (ol.LEGACY_IE_SUPPORT && ol.IS_LEGACY_IE) {
this.ieDblclickListenerKey_ = goog.events.listen(element,
goog.events.EventType.DBLCLICK, this.emulateClick_, false, this);
}
this.relayedListenerKeys_ = [
goog.events.listen(this.pointerEventHandler_,
ol.pointer.EventType.POINTERMOVE,
this.relayEvent_, false, this)
];
};
goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);
@@ -232,7 +212,7 @@ ol.MapBrowserEventHandler.prototype.emulateClick_ = function(browserEvent) {
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) {
ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(browserEvent) {
if (this.down_) {
goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
this.dragListenerKeys_ = null;
@@ -251,28 +231,17 @@ ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) {
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) {
if (!goog.isNull(this.pointerdownListenerKey_)) {
// mouse device detected - unregister the pointerdown and touchstart
// listeners
goog.events.unlistenByKey(this.pointerdownListenerKey_);
this.pointerdownListenerKey_ = null;
goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
goog.events.unlistenByKey(this.touchstartListenerKey_);
this.touchstartListenerKey_ = null;
}
ol.MapBrowserEventHandler.prototype.handlePointerDown_ = function(browserEvent) {
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.DOWN, this.map_, browserEvent);
this.dispatchEvent(newEvent);
this.down_ = browserEvent;
this.dragged_ = false;
this.dragListenerKeys_ = [
goog.events.listen(goog.global.document, goog.events.EventType.MOUSEMOVE,
this.handleMouseMove_, false, this),
goog.events.listen(goog.global.document, goog.events.EventType.MOUSEUP,
this.handleMouseUp_, false, this)
goog.events.listen(this.pointerEventHandler_, ol.MapBrowserEvent.EventType.POINTERMOVE,
this.handlePointerMove_, false, this),
goog.events.listen(this.pointerEventHandler_, ol.MapBrowserEvent.EventType.POINTERUP,
this.handlePointerUp_, false, this)
];
// prevent browser image dragging with the dom renderer
browserEvent.preventDefault();
@@ -283,7 +252,7 @@ ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) {
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) {
ol.MapBrowserEventHandler.prototype.handlePointerMove_ = function(browserEvent) {
var newEvent;
if (!this.dragged_) {
this.dragged_ = true;
@@ -297,159 +266,6 @@ ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) {
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
function(browserEvent) {
if (!goog.isNull(this.mousedownListenerKey_)) {
// pointer device detected - unregister the mousedown and touchstart
// listeners
goog.events.unlistenByKey(this.mousedownListenerKey_);
this.mousedownListenerKey_ = null;
goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
goog.events.unlistenByKey(this.touchstartListenerKey_);
this.touchstartListenerKey_ = null;
}
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
this.dispatchEvent(newEvent);
this.down_ = browserEvent;
this.dragged_ = false;
this.dragListenerKeys_ = [
goog.events.listen(goog.global.document,
goog.events.EventType.MSPOINTERMOVE,
this.handlePointerMove_, false, this),
goog.events.listen(goog.global.document, goog.events.EventType.MSPOINTERUP,
this.handlePointerUp_, false, this)
];
// FIXME check if/when this is necessary
// prevent context menu
browserEvent.preventDefault();
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
function(browserEvent) {
// Fix IE10 on windows Surface : When you tap the tablet, it triggers
// multiple pointermove events between pointerdown and pointerup with
// the exact same coordinates of the pointerdown event. To avoid a
// 'false' touchmove event to be dispatched , we test if the pointer
// effectively moved.
if (browserEvent.clientX != this.down_.clientX ||
browserEvent.clientY != this.down_.clientY) {
this.dragged_ = true;
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
this.dispatchEvent(newEvent);
}
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(browserEvent) {
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
this.dispatchEvent(newEvent);
goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
// We emulate click event on left mouse button click, touch contact, and pen
// contact. isMouseActionButton returns true in these cases (evt.button is set
// to 0).
// See http://www.w3.org/TR/pointerevents/#button-states .
if (!this.dragged_ && browserEvent.isMouseActionButton()) {
goog.asserts.assert(!goog.isNull(this.down_));
this.emulateClick_(this.down_);
}
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) {
if (!goog.isNull(this.mousedownListenerKey_)) {
// touch device detected - unregister the mousedown and pointerdown
// listeners
goog.events.unlistenByKey(this.mousedownListenerKey_);
this.mousedownListenerKey_ = null;
goog.asserts.assert(!goog.isNull(this.pointerdownListenerKey_));
goog.events.unlistenByKey(this.pointerdownListenerKey_);
this.pointerdownListenerKey_ = null;
}
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
this.dispatchEvent(newEvent);
this.down_ = browserEvent;
this.dragged_ = false;
if (goog.isNull(this.dragListenerKeys_)) {
this.dragListenerKeys_ = [
goog.events.listen(goog.global.document, goog.events.EventType.TOUCHMOVE,
this.handleTouchMove_, false, this),
goog.events.listen(goog.global.document, goog.events.EventType.TOUCHEND,
this.handleTouchEnd_, false, this)
];
}
// FIXME check if/when this is necessary
browserEvent.preventDefault();
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) {
this.dragged_ = true;
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
this.dispatchEvent(newEvent);
// Some native android browser triggers mousemove events during small period
// of time. See: https://code.google.com/p/android/issues/detail?id=5491 or
// https://code.google.com/p/android/issues/detail?id=19827
// ex: Galaxy Tab P3110 + Android 4.1.1
browserEvent.preventDefault();
};
/**
* @param {goog.events.BrowserEvent} browserEvent Browser event.
* @private
*/
ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) {
var newEvent = new ol.MapBrowserEvent(
ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
this.dispatchEvent(newEvent);
if (browserEvent.getBrowserEvent().targetTouches.length === 0) {
goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
this.dragListenerKeys_ = null;
}
if (!this.dragged_) {
goog.asserts.assert(!goog.isNull(this.down_));
this.emulateClick_(this.down_);
}
};
/**
* FIXME empty description for jsdoc
*/
@@ -502,13 +318,22 @@ ol.MapBrowserEventHandler.prototype.relayEvent_ = function(browserEvent) {
ol.MapBrowserEvent.EventType = {
CLICK: goog.events.EventType.CLICK,
DBLCLICK: goog.events.EventType.DBLCLICK,
MOUSEMOVE: goog.events.EventType.MOUSEMOVE,
DOWN: 'down',
DRAGSTART: 'dragstart',
DRAG: 'drag',
DRAGEND: 'dragend',
DOWN: 'down',
MOUSEMOVE: goog.events.EventType.MOUSEMOVE,
SINGLECLICK: 'singleclick',
TOUCHSTART: goog.events.EventType.TOUCHSTART,
TOUCHMOVE: goog.events.EventType.TOUCHMOVE,
TOUCHEND: goog.events.EventType.TOUCHEND
TOUCHEND: goog.events.EventType.TOUCHEND,
POINTERMOVE: 'pointermove',
POINTERDOWN: 'pointerdown',
POINTERUP: 'pointerup',
POINTEROVER: 'pointerover',
POINTERENTER: 'pointerenter',
POINTERLEAVE: 'pointerleave',
POINTERCANCEL: 'pointercancel'
};

View File

@@ -0,0 +1,40 @@
goog.provide('ol.pointer.EventSource');
/**
* @param {ol.pointer.PointerEventHandler} dispatcher
* @constructor
*/
ol.pointer.EventSource = function(dispatcher) {
/**
* @type {ol.pointer.PointerEventHandler}
*/
this.dispatcher = dispatcher;
};
/**
* List of events supported by this source.
* @return {Array.<string>} Event names
*/
ol.pointer.EventSource.prototype.getEvents = goog.abstractMethod;
/**
* Returns a mapping between the supported event types and
* the handlers that should handle an event.
* @return {Object.<string, function(Event)>} Event/Handler mapping
*/
ol.pointer.EventSource.prototype.getMapping = goog.abstractMethod;
/**
* Returns the handler that should handle a given event type.
* @param {string} eventType
* @return {function(Event)} Handler
*/
ol.pointer.EventSource.prototype.getHandlerForEvent = function(eventType) {
return this.getMapping()[eventType];
};

View File

@@ -0,0 +1,197 @@
goog.provide('ol.pointer.MouseSource');
goog.require('ol.pointer.EventSource');
/**
* @param {ol.pointer.PointerEventHandler} dispatcher
* @constructor
* @extends {ol.pointer.EventSource}
*/
ol.pointer.MouseSource = function(dispatcher) {
goog.base(this, dispatcher);
this.pointerMap = dispatcher.pointerMap;
// radius around touchend that swallows mouse events
this.DEDUP_DIST = 25;
this.POINTER_ID = 1;
this.POINTER_TYPE = 'mouse';
this.events = [
'mousedown',
'mousemove',
'mouseup',
'mouseover',
'mouseout'
];
this.mapping = {
'mousedown': this.mousedown,
'mousemove': this.mousemove,
'mouseup': this.mouseup,
'mouseover': this.mouseover,
'mouseout': this.mouseout
};
this.lastTouches = [];
};
goog.inherits(ol.pointer.MouseSource, ol.pointer.EventSource);
/** @inheritDoc */
ol.pointer.MouseSource.prototype.getEvents = function() {
return this.events;
};
/** @inheritDoc */
ol.pointer.MouseSource.prototype.getMapping = function() {
return this.mapping;
};
/**
* Collide with the global mouse listener
*
* @private
* @param {goog.events.BrowserEvent} inEvent
* @return {boolean} True, if the event was generated by a touch.
*/
ol.pointer.MouseSource.prototype.isEventSimulatedFromTouch_ =
function(inEvent) {
var lts = this.lastTouches;
var x = inEvent.clientX, y = inEvent.clientY;
for (var i = 0, l = lts.length, t; i < l && (t = lts[i]); i++) {
// simulated mouse events will be swallowed near a primary touchend
var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
if (dx <= this.DEDUP_DIST && dy <= this.DEDUP_DIST) {
return true;
}
}
return false;
};
/**
* Creates a copy of the original event that will be used
* for the fake pointer event.
*
* @private
* @param {goog.events.BrowserEvent} inEvent
* @return {Object}
*/
ol.pointer.MouseSource.prototype.prepareEvent_ = function(inEvent) {
var e = this.dispatcher.cloneEvent(inEvent);
// forward mouse preventDefault
var pd = e.preventDefault;
e.preventDefault = function() {
inEvent.preventDefault();
pd();
};
e.pointerId = this.POINTER_ID;
e.isPrimary = true;
e.pointerType = this.POINTER_TYPE;
return e;
};
/**
* Handler for `mousedown`.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.mousedown = function(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
var p = this.pointerMap.containsKey(this.POINTER_ID);
// TODO(dfreedman) workaround for some elements not sending mouseup
// http://crbug/149091
if (p) {
this.cancel(inEvent);
}
var e = this.prepareEvent_(inEvent);
this.pointerMap.set(this.POINTER_ID, inEvent);
this.dispatcher.down(e);
}
};
/**
* Handler for `mousemove`.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.mousemove = function(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
var e = this.prepareEvent_(inEvent);
this.dispatcher.move(e);
}
};
/**
* Handler for `mouseup`.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.mouseup = function(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
var p = this.pointerMap.get(this.POINTER_ID);
if (p && p.button === inEvent.button) {
var e = this.prepareEvent_(inEvent);
this.dispatcher.up(e);
this.cleanupMouse();
}
}
};
/**
* Handler for `mouseover`.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.mouseover = function(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
var e = this.prepareEvent_(inEvent);
this.dispatcher.enterOver(e);
}
};
/**
* Handler for `mouseout`.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.mouseout = function(inEvent) {
if (!this.isEventSimulatedFromTouch_(inEvent)) {
var e = this.prepareEvent_(inEvent);
this.dispatcher.leaveOut(e);
}
};
/**
* Dispatches a `pointercancel` event.
*
* @param {goog.events.BrowserEvent} inEvent
*/
ol.pointer.MouseSource.prototype.cancel = function(inEvent) {
var e = this.prepareEvent_(inEvent);
this.dispatcher.cancel(e);
this.cleanupMouse();
};
/**
* Remove the mouse from the list of active pointers.
*/
ol.pointer.MouseSource.prototype.cleanupMouse = function() {
this.pointerMap.remove(this.POINTER_ID);
};

View File

@@ -0,0 +1,209 @@
goog.provide('ol.pointer.PointerEvent');
//goog.require('goog.events.Event');
goog.require('goog.events');
/**
* This is the constructor for new PointerEvents.
*
* New Pointer Events must be given a type, and an optional dictionary of
* initialization properties.
*
* Due to certain platform requirements, events returned from the constructor
* identify as MouseEvents.
*
* @constructor
* @extends {Event}
* @param {string} inType The type of the event to create.
* @param {Object.<string, ?>=} opt_inDict An optional dictionary of
* initial event properties.
*/
ol.pointer.PointerEvent = function(inType, opt_inDict) {
opt_inDict = opt_inDict || {};
// According to the w3c spec,
// http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-button
// MouseEvent.button == 0 can mean either no mouse button depressed, or the
// left mouse button depressed.
//
// As of now, the only way to distinguish between the two states of
// MouseEvent.button is by using the deprecated MouseEvent.which property, as
// this maps mouse buttons to positive integers > 0, and uses 0 to mean that
// no mouse button is held.
//
// MouseEvent.which is derived from MouseEvent.button at MouseEvent creation,
// but initMouseEvent does not expose an argument with which to set
// MouseEvent.which. Calling initMouseEvent with a buttonArg of 0 will set
// MouseEvent.button == 0 and MouseEvent.which == 1, breaking the expectations
// of app developers.
//
// The only way to propagate the correct state of MouseEvent.which and
// MouseEvent.button to a new MouseEvent.button == 0 and MouseEvent.which == 0
// is to call initMouseEvent with a buttonArg value of -1.
//
// This is fixed with DOM Level 4's use of buttons
var buttons;
if (opt_inDict.buttons || ol.pointer.PointerEvent.HAS_BUTTONS) {
buttons = opt_inDict.buttons;
} else {
switch (opt_inDict.which) {
case 1: buttons = 1; break;
case 2: buttons = 4; break;
case 3: buttons = 2; break;
default: buttons = 0;
}
}
var e;
if (ol.pointer.PointerEvent.NEW_MOUSE_EVENT) {
e = ol.pointer.PointerEvent.createMouseEvent(inType, opt_inDict);
} else {
e = document.createEvent('MouseEvent');
// import values from the given dictionary
/**
* @type {Object.<string, ?>}
*/
var props = {};
var p;
for (var i = 0; i < ol.pointer.PointerEvent.MOUSE_PROPS.length; i++) {
p = ol.pointer.PointerEvent.MOUSE_PROPS[i];
props[p] = opt_inDict[p] || ol.pointer.PointerEvent.MOUSE_DEFAULTS[i];
}
// define the properties inherited from MouseEvent
e.initMouseEvent(
inType, props.bubbles, props.cancelable, props.view, props.detail,
props.screenX, props.screenY, props.clientX, props.clientY,
props.ctrlKey, props.altKey, props.shiftKey, props.metaKey,
props.button, props.relatedTarget
);
}
// make the event pass instanceof checks
e.__proto__ = ol.pointer.PointerEvent.prototype;
// define the buttons property according to DOM Level 3 spec
if (!ol.pointer.PointerEvent.HAS_BUTTONS) {
// IE 10 has buttons on MouseEvent.prototype as a getter w/o any setting
// mechanism
Object.defineProperty(e, 'buttons',
{get: function() { return buttons; }, enumerable: true});
}
// Spec requires that pointers without pressure specified use 0.5 for down
// state and 0 for up state.
var pressure = 0;
if (opt_inDict.pressure) {
pressure = opt_inDict.pressure;
} else {
pressure = buttons ? 0.5 : 0;
}
// define the properties of the PointerEvent interface
Object.defineProperties(e, {
pointerId: { value: opt_inDict.pointerId || 0, enumerable: true },
width: { value: opt_inDict.width || 0, enumerable: true },
height: { value: opt_inDict.height || 0, enumerable: true },
pressure: { value: pressure, enumerable: true },
tiltX: { value: opt_inDict.tiltX || 0, enumerable: true },
tiltY: { value: opt_inDict.tiltY || 0, enumerable: true },
pointerType: { value: opt_inDict.pointerType || '', enumerable: true },
hwTimestamp: { value: opt_inDict.hwTimestamp || 0, enumerable: true },
isPrimary: { value: opt_inDict.isPrimary || false, enumerable: true }
});
return e;
};
// PointerEvent extends MouseEvent
ol.pointer.PointerEvent.prototype = Object.create(MouseEvent.prototype);
// test for DOM Level 4 Events
/**
* Does the browser support the `MouseEvent` type?
* @type {boolean}
*/
ol.pointer.PointerEvent.NEW_MOUSE_EVENT = false;
/**
* Is the `buttons` property supported?
* @type {boolean}
*/
ol.pointer.PointerEvent.HAS_BUTTONS = false;
/**
* Checks if the `MouseEvent` type is supported.
*/
ol.pointer.PointerEvent.checkNewMouseEvent = function() {
try {
var ev = ol.pointer.PointerEvent.createMouseEvent('click', {buttons: 1});
ol.pointer.PointerEvent.NEW_MOUSE_EVENT = true;
ol.pointer.PointerEvent.HAS_BUTTONS = ev.buttons === 1;
} catch (e) {
}
};
ol.pointer.PointerEvent.checkNewMouseEvent();
/**
* Warning is suppressed because Closure thinks MouseEvent
* has no arguments.
* @param {string} inType The type of the event to create.
* @param {Object} inDict An dictionary of initial event properties.
* @return {MouseEvent}
* @suppress {checkTypes}
*/
ol.pointer.PointerEvent.createMouseEvent = function(inType, inDict) {
return new MouseEvent(inType, inDict);
};
/**
* List of properties to copy when creating an event.
* @type {Array.<string>}
*/
ol.pointer.PointerEvent.MOUSE_PROPS = [
'bubbles',
'cancelable',
'view',
'detail',
'screenX',
'screenY',
'clientX',
'clientY',
'ctrlKey',
'altKey',
'shiftKey',
'metaKey',
'button',
'relatedTarget'
];
/**
* List of default values when creating an event.
*/
ol.pointer.PointerEvent.MOUSE_DEFAULTS = [
false,
false,
null,
null,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null
];

View File

@@ -0,0 +1,509 @@
goog.provide('ol.pointer.PointerEventHandler');
goog.require('goog.debug.Console');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventTarget');
goog.require('goog.structs.Map');
goog.require('ol.pointer.MouseSource');
// goog.require('ol.pointer.MsSource');
// goog.require('ol.pointer.NativeSource');
goog.require('ol.pointer.PointerEvent');
// goog.require('ol.pointer.TouchSource');
goog.require('ol.structs.WeakMap');
/**
* @constructor
* @extends {goog.events.EventTarget}
* @param {Element} element Viewport element.
*/
ol.pointer.PointerEventHandler = function(element) {
goog.base(this);
/**
* @const
* @private
* @type {Element}
*/
this.element_ = element;
/**
* @const
* @type {goog.structs.Map}
*/
this.pointerMap = new goog.structs.Map();
/**
* @const
* @type {ol.structs.WeakMap}
*/
this.targets = new ol.structs.WeakMap();
/**
* @const
* @type {ol.structs.WeakMap}
*/
this.handledEvents = new ol.structs.WeakMap();
this.eventMap = {};
// Scope objects for native events.
// This exists for ease of testing.
this.eventSources = {};
this.eventSourceList = [];
this.boundHandler_ = this.eventHandler_.bind(this);
this.registerSources();
};
goog.inherits(ol.pointer.PointerEventHandler, goog.events.EventTarget);
/**
* Set up the event sources (mouse, touch and native pointers)
* that generate pointer events.
*/
ol.pointer.PointerEventHandler.prototype.registerSources = function() {
if (this.isPointerEnabled_()) {
// this.registerSource('native', new ol.pointer.NativeSource(this));
} else if (this.isMsPointerEnabled_()) {
// this.registerSource('ms', new ol.pointer.MsSource(this));
} else {
var mouseSource = new ol.pointer.MouseSource(this);
this.registerSource('mouse', mouseSource);
if (this.isTouchDefined_()) {
//this.registerSource('touch',
// new ol.pointer.TouchSource(this, mouseSource));
}
}
// register events on the viewport element
this.register_();
};
/**
* @private
* @return {boolean} Returns true if the browser supports
* native pointer events.
*/
ol.pointer.PointerEventHandler.prototype.isPointerEnabled_ = function() {
/* TODO navigation.pointerEnabled is actually not part of the
* spec: https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890#c3
*/
return window.navigator['pointerEnabled'] !== undefined;
};
/**
* @private
* @return {boolean} Returns true if the browser supports
* ms pointer events (IE10).
*/
ol.pointer.PointerEventHandler.prototype.isMsPointerEnabled_ = function() {
return window.navigator['msPointerEnabled'] !== undefined;
};
/**
* @private
* @return {boolean} Returns true if the browser supports
* touch events.
*/
ol.pointer.PointerEventHandler.prototype.isTouchDefined_ = function() {
return window['ontouchstart'] !== undefined;
};
/**
* Add a new event source that will generate pointer events.
*
* @param {string} name A name for the event source
* @param {ol.pointer.EventSource} source
*/
ol.pointer.PointerEventHandler.prototype.registerSource =
function(name, source) {
var s = source;
var newEvents = s.getEvents();
if (newEvents) {
newEvents.forEach(function(e) {
var handler = s.getHandlerForEvent(e);
if (handler) {
this.eventMap[e] = handler.bind(s);
}
}, this);
this.eventSources[name] = s;
this.eventSourceList.push(s);
}
};
/**
* @suppress {undefinedVars}
*/
ol.pointer.PointerEventHandler.prototype.log = function(obj) {
console.log(obj);
}
/**
* Set up the events for all registered event sources.
* @private
*/
ol.pointer.PointerEventHandler.prototype.register_ = function() {
var l = this.eventSourceList.length;
for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
this.addEvents_(es.getEvents());
}
};
/**
* Remove all registered events.
* @private
*/
ol.pointer.PointerEventHandler.prototype.unregister_ = function() {
var l = this.eventSourceList.length;
for (var i = 0, es; (i < l) && (es = this.eventSourceList[i]); i++) {
this.removeEvents_(es.getEvents());
}
};
/**
* Calls the right handler for a new event.
* @private
* @param {goog.events.BrowserEvent} inEvent Browser event.
*/
ol.pointer.PointerEventHandler.prototype.eventHandler_ = function(inEvent) {
// This is used to prevent multiple dispatch of pointerevents from
// platform events. This can happen when two elements in different scopes
// are set up to create pointer events, which is relevant to Shadow DOM.
if (this.handledEvents['get'](inEvent)) {
return;
}
var type = inEvent.type;
var handler = this.eventMap[type];
if (handler) {
handler(inEvent);
}
this.handledEvents['set'](inEvent, true);
};
/**
* Setup listeners for the given events.
* @private
* @param {Array.<string>} events List of events.
*/
ol.pointer.PointerEventHandler.prototype.addEvents_ = function(events) {
events.forEach(function(eventName) {
goog.events.listen(this.element_, eventName,
this.boundHandler_);
}, this);
};
/**
* Unregister listeners for the given events.
* @private
* @param {Array.<string>} events List of events.
*/
ol.pointer.PointerEventHandler.prototype.removeEvents_ = function(events) {
events.forEach(function(e) {
goog.events.unlisten(this.element_, e,
this.boundHandler_);
}, this);
};
/**
* Returns a snapshot of inEvent, with writable properties.
*
* @param {goog.events.BrowserEvent} inEvent An event that contains
* properties to copy.
* @return {Object} An object containing shallow copies of
* `inEvent`'s properties.
*/
ol.pointer.PointerEventHandler.prototype.cloneEvent = function(inEvent) {
var eventCopy = {}, p;
for (var i = 0; i < ol.pointer.CLONE_PROPS.length; i++) {
p = ol.pointer.CLONE_PROPS[i];
eventCopy[p] = inEvent[p] || ol.pointer.CLONE_DEFAULTS[i];
}
// keep the semantics of preventDefault
if (inEvent.preventDefault) {
eventCopy.preventDefault = function() {
inEvent.preventDefault();
};
}
return eventCopy;
};
// EVENTS
/**
* Triggers a 'pointerdown' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.down = function(inEvent) {
this.fireEvent('pointerdown', inEvent);
};
/**
* Triggers a 'pointermove' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.move = function(inEvent) {
this.fireEvent('pointermove', inEvent);
};
/**
* Triggers a 'pointerup' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.up = function(inEvent) {
this.fireEvent('pointerup', inEvent);
};
/**
* Triggers a 'pointerenter' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.enter = function(inEvent) {
inEvent.bubbles = false;
this.fireEvent('pointerenter', inEvent);
};
/**
* Triggers a 'pointerleave' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.leave = function(inEvent) {
inEvent.bubbles = false;
this.fireEvent('pointerleave', inEvent);
};
/**
* Triggers a 'pointerover' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.over = function(inEvent) {
inEvent.bubbles = true;
this.fireEvent('pointerover', inEvent);
};
/**
* Triggers a 'pointerout' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.out = function(inEvent) {
inEvent.bubbles = true;
this.fireEvent('pointerout', inEvent);
};
/**
* Triggers a 'pointercancel' event.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.cancel = function(inEvent) {
this.fireEvent('pointercancel', inEvent);
};
/**
* Triggers a combination of 'pointerout' and 'pointerleave' events.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.leaveOut = function(inEvent) {
this.out(inEvent);
if (!this.contains_(inEvent.target, inEvent.relatedTarget)) {
this.leave(inEvent);
}
};
/**
* Triggers a combination of 'pointerover' and 'pointerevents' events.
* @param {Object} inEvent
*/
ol.pointer.PointerEventHandler.prototype.enterOver = function(inEvent) {
this.over(inEvent);
if (!this.contains_(inEvent.target, inEvent.relatedTarget)) {
this.enter(inEvent);
}
};
/**
* @private
* @param {Element} container
* @param {Element} contained
* @return {boolean} Returns true if the container element
* contains the other element.
*/
ol.pointer.PointerEventHandler.prototype.contains_ =
function(container, contained) {
return container.contains(contained);
};
// EVENT CREATION AND TRACKING
/**
* Creates a new Event of type `inType`, based on the information in
* `inEvent`.
*
* @param {string} inType A string representing the type of event to create.
* @param {Object} inEvent A platform event with a target.
* @return {ol.pointer.PointerEvent} A PointerEvent of type `inType`.
*/
ol.pointer.PointerEventHandler.prototype.makeEvent = function(inType, inEvent) {
// relatedTarget must be null if pointer is captured
if (this.captureInfo) {
inEvent.relatedTarget = null;
}
var e = new ol.pointer.PointerEvent(inType, inEvent);
if (inEvent.preventDefault) {
e.preventDefault = inEvent.preventDefault;
}
this.targets['set'](e, this.targets['get'](inEvent) || inEvent.target);
return e;
};
/**
* Make and dispatch an event in one call.
* @param {string} inType A string representing the type of event.
* @param {Object} inEvent A platform event with a target.
*/
ol.pointer.PointerEventHandler.prototype.fireEvent = function(inType, inEvent) {
var e = this.makeEvent(inType, inEvent);
var browserEvent = new goog.events.BrowserEvent(e);
this.dispatchEvent(browserEvent);
};
/**
* Re-fires a native pointer event.
* @param {Event} nativeEvent A platform event with a target.
*/
ol.pointer.PointerEventHandler.prototype.fireNativeEvent =
function(nativeEvent) {
var browserEvent = new goog.events.BrowserEvent(nativeEvent);
this.dispatchEvent(browserEvent);
};
/**
* Constants for event names.
* @enum {string}
*/
ol.pointer.EventType = {
POINTERMOVE: 'pointermove',
POINTERDOWN: 'pointerdown',
POINTERUP: 'pointerup',
POINTEROVER: 'pointerover',
POINTERENTER: 'pointerenter',
POINTERLEAVE: 'pointerleave',
POINTERCANCEL: 'pointercancel'
};
/**
* List of properties to copy when cloning an event.
* @type {Array.<string>}
*/
ol.pointer.CLONE_PROPS = [
// MouseEvent
'bubbles',
'cancelable',
'view',
'detail',
'screenX',
'screenY',
'clientX',
'clientY',
'ctrlKey',
'altKey',
'shiftKey',
'metaKey',
'button',
'relatedTarget',
// DOM Level 3
'buttons',
// PointerEvent
'pointerId',
'width',
'height',
'pressure',
'tiltX',
'tiltY',
'pointerType',
'hwTimestamp',
'isPrimary',
// event instance
'type',
'target',
'currentTarget',
'which'
];
/**
* List of default values when cloning an event.
*/
ol.pointer.CLONE_DEFAULTS = [
// MouseEvent
false,
false,
null,
null,
0,
0,
0,
0,
false,
false,
false,
false,
0,
null,
// DOM Level 3
0,
// PointerEvent
0,
0,
0,
0,
0,
0,
'',
0,
false,
// event instance
'',
null,
null,
0
];

77
src/ol/structs/weakmap.js Normal file
View File

@@ -0,0 +1,77 @@
// Based on https://github.com/Polymer/WeakMap
/*
* Copyright 2012 The Polymer Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
goog.provide('ol.structs.WeakMap');
/**
* @suppress {undefinedVars}
* @return {boolean} Is `WeakMap` already defined?
*/
ol.structs.isWeakMapUndefined = function() {
return typeof WeakMap === 'undefined';
};
if (ol.structs.isWeakMapUndefined()) {
/**
* @constructor
*/
ol.structs.WeakMap = function() {
this.name = '__st' + (Math.random() * 1e9 >>> 0) +
(ol.structs.WeakMap.counter++ + '__');
};
/**
* @param {*} key
* @param {*} value
*/
ol.structs.WeakMap.prototype['set'] = function(key, value) {
var entry = key[this.name];
if (entry && entry[0] === key)
entry[1] = value;
else
ol.structs.WeakMap.defineProperty(key, this.name,
{value: [key, value], writable: true});
};
/**
* @param {*} key
* @return {*}
*/
ol.structs.WeakMap.prototype['get'] = function(key) {
var entry;
return (entry = key[this.name]) && entry[0] === key ?
entry[1] : undefined;
};
/**
* @this {ol.structs.WeakMap}
* @param {*} key
*/
ol.structs.WeakMap.prototype['delete'] = function(key) {
this['set'](key, undefined);
};
} else {
ol.structs.WeakMap = WeakMap;
}
/**
* @type {function(...)}
*/
ol.structs.WeakMap.defineProperty = Object.defineProperty;
/**
* @type {number}
*/
ol.structs.WeakMap.counter = Date.now() % 1e9;