From 3d62b67172b4ab509ba506523baa3a13bb73da1c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 28 Sep 2012 13:01:56 +0200 Subject: [PATCH] Introducing ol.overlay.Overlay and an overlayContainer ol.overlay.Overlay can be used to bind DOM elements to a coordinate on the map. It has positioning options to support e.g. popups or image markers that have an anchor at the bottom and an unknown size. The overlayContainer stops propagation on mousedown and touchstart events, so clicks and gestures on overlays don't trigger any MapBrowserEvent. To make this work reliably, we now only fire dblclick in mapbrowserevent.js when there was a previous mousedown or touchstart. The default container for controls is now the overlayContainer. --- css/ol.css | 2 +- demos/full-screen/full-screen.js | 7 ++ src/ol/control/control.js | 3 +- src/ol/control/zoom.js | 5 +- src/ol/map.js | 35 ++++-- src/ol/mapbrowserevent.js | 3 +- src/ol/overlay/overlay.js | 185 +++++++++++++++++++++++++++++++ src/ol/renderer/dom/map.js | 4 +- src/ol/renderer/webgl/map.js | 3 +- 9 files changed, 225 insertions(+), 22 deletions(-) create mode 100644 src/ol/overlay/overlay.js diff --git a/css/ol.css b/css/ol.css index b5c2bd97fb..895ce60c8a 100644 --- a/css/ol.css +++ b/css/ol.css @@ -1,4 +1,4 @@ -.ol-viewport { +.ol-viewport .ol-unselectable { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; diff --git a/demos/full-screen/full-screen.js b/demos/full-screen/full-screen.js index c7ce3c6796..979290ea66 100644 --- a/demos/full-screen/full-screen.js +++ b/demos/full-screen/full-screen.js @@ -5,6 +5,7 @@ goog.require('ol.Collection'); goog.require('ol.Coordinate'); goog.require('ol.Map'); goog.require('ol.MapOptions'); // FIXME this should not be required +goog.require('ol.overlay.Overlay'); goog.require('ol.source.MapQuestOpenAerial'); @@ -22,3 +23,9 @@ var map = new ol.Map(document.getElementById('map'), { layers: new ol.Collection([layer]), zoom: 2 }); +var vienna = new ol.overlay.Overlay({ + map: map, + coordinate: ol.Projection.transformWithCodes( + new ol.Coordinate(16, 48), 'EPSG:4326', 'EPSG:3857'), + element: document.getElementById('vienna') +}); diff --git a/src/ol/control/control.js b/src/ol/control/control.js index b974259b5d..e4f239c536 100644 --- a/src/ol/control/control.js +++ b/src/ol/control/control.js @@ -67,7 +67,8 @@ ol.control.Control.prototype.setMap = function(map) { } this.map_ = map; if (!goog.isNull(this.map_)) { - var target = goog.isDef(this.target_) ? this.target_ : map.getViewport(); + var target = goog.isDef(this.target_) ? + this.target_ : map.getOverlayContainer(); goog.dom.appendChild(target, this.element); } }; diff --git a/src/ol/control/zoom.js b/src/ol/control/zoom.js index 754bae35f8..583532e567 100644 --- a/src/ol/control/zoom.js +++ b/src/ol/control/zoom.js @@ -42,10 +42,7 @@ ol.control.Zoom = function(zoomOptions) { goog.events.listen(outElement, eventType, this.handleOut_, false, this); var element = goog.dom.createDom( - goog.dom.TagName.DIV, 'ol-zoom', inElement, outElement); - goog.events.listen( - element, ol.BrowserFeature.HAS_TOUCH ? goog.events.EventType.TOUCHSTART : - goog.events.EventType.MOUSEDOWN, goog.events.Event.stopPropagation); + goog.dom.TagName.DIV, 'ol-zoom ol-unselectable', inElement, outElement); goog.base(this, { element: element, diff --git a/src/ol/map.js b/src/ol/map.js index 10a7c50456..b667bf470f 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -24,6 +24,7 @@ goog.require('goog.functions'); goog.require('goog.fx.anim'); goog.require('goog.fx.anim.Animated'); goog.require('goog.object'); +goog.require('ol.BrowserFeature'); goog.require('ol.Collection'); goog.require('ol.Color'); goog.require('ol.Constraints'); @@ -63,14 +64,6 @@ ol.MapProperty = { }; -/** - * @enum {number} - */ -ol.MapPaneZIndex = { - VIEWPORT: 1000 -}; - - /** * @constructor @@ -145,15 +138,25 @@ ol.Map = function(container, mapOptionsLiteral) { * @private * @type {Element} */ - this.viewport_ = goog.dom.createElement(goog.dom.TagName.DIV); - this.viewport_.className = 'ol-viewport'; + this.viewport_ = goog.dom.createDom(goog.dom.TagName.DIV, 'ol-viewport'); this.viewport_.style.position = 'relative'; this.viewport_.style.overflow = 'hidden'; this.viewport_.style.width = '100%'; this.viewport_.style.height = '100%'; - this.viewport_.style.zIndex = ol.MapPaneZIndex.VIEWPORT; goog.dom.appendChild(container, this.viewport_); + /** + * @private + * @type {Element} + */ + this.overlayContainer_ = goog.dom.createDom(goog.dom.TagName.DIV, + 'ol-overlaycontainer'); + goog.events.listen(this.overlayContainer_, + ol.BrowserFeature.HAS_TOUCH ? + goog.events.EventType.TOUCHSTART : goog.events.EventType.MOUSEDOWN, + goog.events.Event.stopPropagation); + goog.dom.appendChild(this.viewport_, this.overlayContainer_); + var mapBrowserEventHandler = new ol.MapBrowserEventHandler(this); goog.events.listen(mapBrowserEventHandler, [ ol.MapBrowserEvent.EventType.CLICK, @@ -504,6 +507,16 @@ ol.Map.prototype.getViewport = function() { }; +/** + * @return {Element} The map's overlay container. Elements added to this + * container won't let mousedown and touchstart events through to the map, so + * clicks and gestures on an overlay don't trigger any MapBrowserEvent. + */ +ol.Map.prototype.getOverlayContainer = function() { + return this.overlayContainer_; +}; + + /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @param {string=} opt_type Type. diff --git a/src/ol/mapbrowserevent.js b/src/ol/mapbrowserevent.js index 21561f54d9..fdfe8668ce 100644 --- a/src/ol/mapbrowserevent.js +++ b/src/ol/mapbrowserevent.js @@ -170,7 +170,6 @@ ol.MapBrowserEventHandler.prototype.click_ = function(browserEvent) { this.touchEnableBrowserEvent_(browserEvent); var newEvent = new ol.MapBrowserEvent( ol.MapBrowserEvent.EventType.CLICK, this.map_, browserEvent); - this.down_ = null; this.dispatchEvent(newEvent); } }; @@ -181,7 +180,7 @@ ol.MapBrowserEventHandler.prototype.click_ = function(browserEvent) { * @private */ ol.MapBrowserEventHandler.prototype.dblclick_ = function(browserEvent) { - if (!this.dragged_) { + if (!this.dragged_ && this.down_) { var now = new Date().getTime(); if (!this.timestamp_ || now - this.timestamp_ > 250) { this.timestamp_ = now; diff --git a/src/ol/overlay/overlay.js b/src/ol/overlay/overlay.js new file mode 100644 index 0000000000..6b501b184f --- /dev/null +++ b/src/ol/overlay/overlay.js @@ -0,0 +1,185 @@ +goog.provide('ol.overlay.Overlay'); +goog.provide('ol.overlay.OverlayOptions'); +goog.provide('ol.overlay.OverlayPositioning'); + +goog.require('goog.events'); +goog.require('goog.style'); +goog.require('ol.Map'); +goog.require('ol.MapProperty'); + + +/** + * @typedef {{coordinate: (ol.Coordinate|undefined), + * element: (Element|undefined), + * map: (ol.Map|undefined), + * positioning: (Array.|undefined)}} + */ +ol.overlay.OverlayOptions; + + + +/** + * @constructor + * @param {ol.overlay.OverlayOptions} overlayOptions Overlay options. + */ +ol.overlay.Overlay = function(overlayOptions) { + + /** + * @type {ol.Coordinate} + * @private + */ + this.coordinate_ = null; + + /** + * @type {Element} + * @private + */ + this.element_ = null; + + /** + * @private + * @type {ol.Map} + */ + this.map_ = null; + + /** + * @type {Array.} + * @private + */ + this.positioning_ = [ + ol.overlay.OverlayPositioning.LEFT, + ol.overlay.OverlayPositioning.BOTTOM + ]; + + /** + * @private + * @type {Array.} + */ + this.mapListenerKeys_ = []; + + if (goog.isDef(overlayOptions.coordinate)) { + this.setCoordinate(overlayOptions.coordinate); + } + if (goog.isDef(overlayOptions.element)) { + this.setElement(overlayOptions.element); + } + if (goog.isDef(overlayOptions.map)) { + this.setMap(overlayOptions.map); + } + if (goog.isDef(overlayOptions.positioning)) { + this.setPositioning(overlayOptions.positioning); + } +}; + + +/** + * @param {ol.Coordinate} coordinate Coordinate for the overlay's position on + * the map. + */ +ol.overlay.Overlay.prototype.setCoordinate = function(coordinate) { + this.coordinate_ = coordinate; + this.updatePixelPosition_(); +}; + + +/** + * @param {Element} element The DOM element for the overlay. + */ +ol.overlay.Overlay.prototype.setElement = function(element) { + if (this.element_) { + goog.dom.removeNode(this.element_); + } + this.element_ = element; + if (this.map_) { + goog.dom.append(/** @type {!Node} */ (this.map_.getOverlayContainer()), + this.element_); + } + this.updatePixelPosition_(); +}; + + +/** + * @return {Element} The DOM element for the overlay. + */ +ol.overlay.Overlay.prototype.getElement = function() { + return this.element_; +}; + + +/** + * @param {ol.Map} map Map. + */ +ol.overlay.Overlay.prototype.setMap = function(map) { + this.map_ = map; + goog.array.forEach(this.mapListenerKeys_, goog.events.unlistenByKey); + if (this.element_) { + this.setElement(this.element_); + } + this.mapListenerKeys_ = map ? [ + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.CENTER), + this.updatePixelPosition_, false, this), + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.RESOLUTION), + this.updatePixelPosition_, false, this), + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.ROTATION), + this.updatePixelPosition_, false, this), + goog.events.listen( + map, ol.Object.getChangedEventType(ol.MapProperty.SIZE), + this.updatePixelPosition_, false, this) + ] : []; + this.updatePixelPosition_(); +}; + + +/** + * @return {ol.Map} Map. + */ +ol.overlay.Overlay.prototype.getMap = function() { + return this.map_; +}; + + +/** + * Set the CSS properties to use for x- and y-positioning of the element. If + * not set, the default is {@code ['left', 'bottom']}. + * @param {Array.} positioning The positioning. + */ +ol.overlay.Overlay.prototype.setPositioning = function(positioning) { + this.positioning_ = positioning; + this.updatePixelPosition_(); +}; + + +/** + * @private + */ +ol.overlay.Overlay.prototype.updatePixelPosition_ = function() { + if (!goog.isNull(this.map_) && !goog.isNull(this.coordinate_) && + !goog.isNull(this.element_)) { + var pixel = this.map_.getPixelFromCoordinate(this.coordinate_); + var mapSize = this.map_.get(ol.MapProperty.SIZE); + var x = Math.round(pixel.x); + if (this.positioning_[0] === ol.overlay.OverlayPositioning.RIGHT) { + x = mapSize.width - x; + } + var y = Math.round(pixel.y); + if (this.positioning_[1] === ol.overlay.OverlayPositioning.BOTTOM) { + y = mapSize.height - y; + } + goog.style.setStyle(this.element_, this.positioning_[0], x + 'px'); + goog.style.setStyle(this.element_, this.positioning_[1], y + 'px'); + } +}; + + +/** + * @enum {string} + */ +ol.overlay.OverlayPositioning = { + LEFT: 'left', + RIGHT: 'right', + TOP: 'top', + BOTTOM: 'bottom' +}; diff --git a/src/ol/renderer/dom/map.js b/src/ol/renderer/dom/map.js index 407c13ebc7..1ba0ce0d7d 100644 --- a/src/ol/renderer/dom/map.js +++ b/src/ol/renderer/dom/map.js @@ -30,13 +30,13 @@ ol.renderer.dom.Map = function(container, map) { * @private */ this.layersPane_ = goog.dom.createElement(goog.dom.TagName.DIV); - this.layersPane_.className = 'ol-layers-pane'; + this.layersPane_.className = 'ol-layers-pane ol-unselectable'; var style = this.layersPane_.style; style.position = 'absolute'; style.width = '100%'; style.height = '100%'; - goog.dom.appendChild(container, this.layersPane_); + goog.dom.insertChildAt(container, this.layersPane_, 0); /** * @type {Object} diff --git a/src/ol/renderer/webgl/map.js b/src/ol/renderer/webgl/map.js index ddb11d00f6..e41e79a838 100644 --- a/src/ol/renderer/webgl/map.js +++ b/src/ol/renderer/webgl/map.js @@ -151,7 +151,8 @@ ol.renderer.webgl.Map = function(container, map) { this.canvas_ = goog.dom.createElement(goog.dom.TagName.CANVAS); this.canvas_.height = container.clientHeight; this.canvas_.width = container.clientWidth; - goog.dom.appendChild(container, this.canvas_); + this.canvas_.className = 'ol-unselectable'; + goog.dom.insertChildAt(container, this.canvas_, 0); /** * @private