diff --git a/css/ol.css b/css/ol.css index 5dc3fe2d68..8b3c61de35 100644 --- a/css/ol.css +++ b/css/ol.css @@ -86,24 +86,25 @@ -webkit-tap-highlight-color: rgba(0,0,0,0); } -.ol-zoom, -.ol-zoom-extent, -.ol-full-screen { +.ol-control { position: absolute; background-color: #eee; background-color: rgba(255,255,255,0.4); border-radius: 4px; padding: 2px; } -.ol-zoom:hover, -.ol-zoom-extent:hover, -.ol-full-screen:hover { +.ol-control:hover { background-color: rgba(255,255,255,0.6); } .ol-zoom { top: .5em; left: .5em; } +.ol-rotate { + top: .5em; + right: .5em; + transition: opacity .25s; +} .ol-zoom-extent { top: 4.643em; left: .5em; @@ -113,16 +114,12 @@ top: .5em; } @media print { - .ol-zoom, - .ol-zoom-extent, - .ol-full-screen { + .ol-control { display: none; } } -.ol-zoom button, -.ol-zoom-extent button, -.ol-full-screen button { +.ol-control button { display: block; margin: 1px; padding: 0; @@ -137,30 +134,29 @@ background-color: #7b98bc; background-color: rgba(0,60,136,0.5); border: none; + border-radius: 2px; } -.ol-zoom button::-moz-focus-inner, -.ol-zoom-extent button::-moz-focus-inner, -.ol-full-screen button::-moz-focus-inner { +.ol-control button::-moz-focus-inner { border: none; padding: 0; } .ol-zoom-extent button { line-height: 1.4em; } -.ol-touch .ol-zoom button, -.ol-touch .ol-full-screen button, -.ol-touch .ol-zoom-extent button { +.ol-compass { + display: block; + font-family: Arial; + font-weight: normal; + font-size: 1.2em; +} +.ol-touch .ol-control button { font-size: 1.5em; } .ol-touch .ol-zoom-extent { top: 5.5em; } -.ol-zoom button:hover, -.ol-zoom button:focus, -.ol-zoom-extent button:hover, -.ol-zoom-extent button:focus, -.ol-full-screen button:hover, -.ol-full-screen button:focus { +.ol-control button:hover, +.ol-control button:focus { text-decoration: none; background-color: #4c6079; background-color: rgba(0,60,136,0.7); @@ -168,10 +164,10 @@ .ol-zoom-extent button:after { content: "E"; } -.ol-zoom-in { +.ol-zoom .ol-zoom-in { border-radius: 2px 2px 0 0; } -.ol-zoom-out { +.ol-zoom .ol-zoom-out { border-radius: 0 0 2px 2px; } button.ol-full-screen-false:after { @@ -228,6 +224,8 @@ button.ol-full-screen-true:after { .ol-zoom .ol-has-tooltip:focus [role=tooltip] { top: 1.1em; } +.ol-rotate .ol-has-tooltip:hover [role=tooltip], +.ol-rotate .ol-has-tooltip:focus [role=tooltip], .ol-full-screen .ol-has-tooltip:hover [role=tooltip], .ol-full-screen .ol-has-tooltip:focus [role=tooltip] { right: 2.2em; @@ -239,13 +237,11 @@ button.ol-full-screen-true:after { position: absolute; top: 4.5em; left: .5em; - background: #eee; background: rgba(255, 255, 255, 0.4); border-radius: 4px; outline: none; overflow: hidden; - width: 1.5675em; height: 200px; padding: 3px; @@ -260,7 +256,6 @@ button.ol-full-screen-true:after { outline: none; overflow: hidden; cursor: pointer; - font-size: 1.14em; height: 1em; width: 1.375em; @@ -275,11 +270,9 @@ button.ol-full-screen-true:after { width: 1.8em; } -.ol-zoom-extent button, +.ol-control button, .ol-attribution, -.ol-full-screen button, .ol-scale-line-inner, -.ol-zoom button, .ol-has-tooltip [role=tooltip] { font-family: 'Lucida Grande',Verdana,Geneva,Lucida,Arial,Helvetica,sans-serif; } diff --git a/examples/full-screen.html b/examples/full-screen.html index a1cf2a3c38..b119431290 100644 --- a/examples/full-screen.html +++ b/examples/full-screen.html @@ -21,6 +21,9 @@ .map:full-screen { height: 100%; } + .ol-rotate { + top: 3em; + } Full screen control example diff --git a/externs/olx.js b/externs/olx.js index a0c3d4933b..940312ecfd 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -683,6 +683,7 @@ olx.control.ControlOptions.prototype.target; * logo: (boolean|undefined), * logoOptions: (olx.control.LogoOptions|undefined), * zoom: (boolean|undefined), + * rotateOptions: (olx.control.RotateOptions|undefined)}} * zoomOptions: (olx.control.ZoomOptions|undefined)}} * @todo api */ @@ -724,6 +725,13 @@ olx.control.DefaultsOptions.prototype.logoOptions; olx.control.DefaultsOptions.prototype.zoom; +/** + * Rotate options. + * @type {olx.control.RotateOptions|undefined} + */ +olx.control.DefaultsOptions.prototype.rotateOptions; + + /** * Zoom options. * @type {olx.control.ZoomOptions|undefined} @@ -875,6 +883,61 @@ olx.control.ScaleLineOptions.prototype.target; olx.control.ScaleLineOptions.prototype.units; +/** + * @typedef {{duration: (number|undefined), + * className: (string|undefined), + * label: (string|undefined), + * tipLabel: (string|undefined), + * target: (Element|undefined), + * autoHide: (boolean|undefined), + * duration: (number|undefined)}} + * @todo stability experimental + */ +olx.control.RotateOptions; + + +/** + * CSS class name. Default is `ol-rotate`. + * @type {string|undefined} + */ +olx.control.RotateOptions.prototype.className; + + +/** + * Text label to use for the rotate button. Default is `⇧` + * @type {string|undefined} + */ +olx.control.RotateOptions.prototype.label; + + +/** + * Text label to use for the rotate tip. Default is `Reset rotation` + * @type {string|undefined} + */ +olx.control.RotateOptions.prototype.tipLabel; + + +/** + * Animation duration in milliseconds. Default is `250`. + * @type {number|undefined} + */ +olx.control.RotateOptions.prototype.duration; + + +/** + * Hide the control when rotation is 0. Default is `true`. + * @type {boolean|undefined} + */ +olx.control.RotateOptions.prototype.autoHide; + + +/** + * Target. + * @type {Element|undefined} + */ +olx.control.RotateOptions.prototype.target; + + /** * @typedef {{duration: (number|undefined), * className: (string|undefined), diff --git a/src/ol/control/controldefaults.js b/src/ol/control/controldefaults.js index 667d63eee3..d75dd5c114 100644 --- a/src/ol/control/controldefaults.js +++ b/src/ol/control/controldefaults.js @@ -3,6 +3,7 @@ goog.provide('ol.control'); goog.require('ol.Collection'); goog.require('ol.control.Attribution'); goog.require('ol.control.Logo'); +goog.require('ol.control.Rotate'); goog.require('ol.control.Zoom'); @@ -20,25 +21,25 @@ ol.control.defaults = function(opt_options) { var zoomControl = goog.isDef(options.zoom) ? options.zoom : true; if (zoomControl) { - var zoomControlOptions = goog.isDef(options.zoomOptions) ? - options.zoomOptions : undefined; - controls.push(new ol.control.Zoom(zoomControlOptions)); + controls.push(new ol.control.Zoom(options.zoomOptions)); + } + + var rotateControl = goog.isDef(options.rotate) ? + options.rotate : true; + if (rotateControl) { + controls.push(new ol.control.Rotate(options.rotateOptions)); } var attributionControl = goog.isDef(options.attribution) ? options.attribution : true; if (attributionControl) { - var attributionControlOptions = goog.isDef(options.attributionOptions) ? - options.attributionOptions : undefined; - controls.push(new ol.control.Attribution(attributionControlOptions)); + controls.push(new ol.control.Attribution(options.attributionOptions)); } var logoControl = goog.isDef(options.logo) ? options.logo : true; if (logoControl) { - var logoControlOptions = goog.isDef(options.logoOptions) ? - options.logoOptions : undefined; - controls.push(new ol.control.Logo(logoControlOptions)); + controls.push(new ol.control.Logo(options.logoOptions)); } return controls; diff --git a/src/ol/control/fullscreencontrol.js b/src/ol/control/fullscreencontrol.js index 0eec49e02d..569a4c124d 100644 --- a/src/ol/control/fullscreencontrol.js +++ b/src/ol/control/fullscreencontrol.js @@ -64,10 +64,10 @@ ol.control.FullScreen = function(opt_options) { googx.dom.fullscreen.EventType.CHANGE, this.handleFullScreenChange_, false, this); - var element = goog.dom.createDom(goog.dom.TagName.DIV, { - 'class': this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + - (!googx.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : '') - }, button); + var cssClasses = this.cssClassName_ + ' ' + ol.css.CLASS_UNSELECTABLE + + ' ' + ol.css.CLASS_CONTROL + + (!googx.dom.fullscreen.isSupported() ? ol.css.CLASS_UNSUPPORTED : ''); + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); goog.base(this, { element: element, diff --git a/src/ol/control/rotatecontrol.js b/src/ol/control/rotatecontrol.js new file mode 100644 index 0000000000..8596e6dbb3 --- /dev/null +++ b/src/ol/control/rotatecontrol.js @@ -0,0 +1,139 @@ +// FIXME works for View2D only + +goog.provide('ol.control.Rotate'); + +goog.require('goog.asserts'); +goog.require('goog.dom'); +goog.require('goog.dom.TagName'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.View2D'); +goog.require('ol.animation'); +goog.require('ol.control.Control'); +goog.require('ol.css'); +goog.require('ol.easing'); +goog.require('ol.pointer.PointerEventHandler'); + + + +/** + * Create a new control with a button, to reset rotation to 0. + * To style this control use css selector `.ol-rotate`. + * @constructor + * @extends {ol.control.Control} + * @param {olx.control.RotateOptions=} opt_options Rotate options. + * @todo api + */ +ol.control.Rotate = function(opt_options) { + + var options = goog.isDef(opt_options) ? opt_options : {}; + + var className = goog.isDef(options.className) ? + options.className : 'ol-rotate'; + + var label = goog.dom.createDom(goog.dom.TagName.SPAN, + { 'class': 'ol-compass' }, + goog.isDef(options.label) ? options.label : '\u21E7'); + this.label_ = label; + + var tipLabel = goog.isDef(options.tipLabel) ? + options.tipLabel : 'Reset rotation'; + + var tip = goog.dom.createDom(goog.dom.TagName.SPAN, { + 'role' : 'tooltip' + }, tipLabel); + var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { + 'class': className + '-reset ol-has-tooltip', + 'name' : 'ResetRotation', + 'type' : 'button' + }, tip, label); + + var handler = new ol.pointer.PointerEventHandler(button); + this.registerDisposable(handler); + goog.events.listen(handler, ol.pointer.EventType.POINTERUP, + ol.control.Rotate.prototype.resetNorth_, false, this); + + goog.events.listen(button, [ + goog.events.EventType.MOUSEOUT, + goog.events.EventType.FOCUSOUT + ], function() { + this.blur(); + }, false); + + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); + + goog.base(this, { + element: element, + target: options.target + }); + + /** + * @type {number} + * @private + */ + this.duration_ = goog.isDef(options.duration) ? options.duration : 250; + + /** + * @type {boolean} + * @private + */ + this.autoHide_ = goog.isDef(options.autoHide) ? options.autoHide : true; + + element.style.opacity = (this.autoHide_) ? 0 : 1; + +}; +goog.inherits(ol.control.Rotate, ol.control.Control); + + +/** + * @param {ol.pointer.PointerEvent} pointerEvent The pointer event to handle. + * @private + */ +ol.control.Rotate.prototype.resetNorth_ = function(pointerEvent) { + pointerEvent.browserEvent.preventDefault(); + // prevent the anchor from getting appended to the url + var map = this.getMap(); + // FIXME works for View2D only + var view = map.getView(); + goog.asserts.assert(goog.isDef(view)); + var view2D = view.getView2D(); + goog.asserts.assertInstanceof(view2D, ol.View2D); + var currentRotation = view2D.getRotation(); + while (currentRotation < -Math.PI) { + currentRotation += 2 * Math.PI; + } + while (currentRotation > Math.PI) { + currentRotation -= 2 * Math.PI; + } + if (goog.isDef(currentRotation)) { + if (this.duration_ > 0) { + map.beforeRender(ol.animation.rotate({ + rotation: currentRotation, + duration: this.duration_, + easing: ol.easing.easeOut + })); + } + view2D.setRotation(0); + } +}; + + +/** + * @inheritDoc + */ +ol.control.Rotate.prototype.handleMapPostrender = function(mapEvent) { + var frameState = mapEvent.frameState; + if (goog.isNull(frameState)) { + return; + } + var rotation = frameState.view2DState.rotation; + var transform = 'rotate(' + rotation * 360 / (Math.PI * 2) + 'deg)'; + if (this.autoHide_) { + this.element.style.opacity = (rotation === 0) ? 0 : 1; + } + this.label_.style.msTransform = transform; + this.label_.style.webkitTransform = transform; + this.label_.style.transform = transform; +}; diff --git a/src/ol/control/zoomcontrol.js b/src/ol/control/zoomcontrol.js index f36794036c..4b8fe5154e 100644 --- a/src/ol/control/zoomcontrol.js +++ b/src/ol/control/zoomcontrol.js @@ -85,7 +85,8 @@ ol.control.Zoom = function(opt_options) { this.blur(); }, false); - var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE; + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, inElement, outElement); diff --git a/src/ol/control/zoomtoextentcontrol.js b/src/ol/control/zoomtoextentcontrol.js index d87a050d7b..03aa7fbbea 100644 --- a/src/ol/control/zoomtoextentcontrol.js +++ b/src/ol/control/zoomtoextentcontrol.js @@ -39,14 +39,10 @@ ol.control.ZoomToExtent = function(opt_options) { var tip = goog.dom.createDom(goog.dom.TagName.SPAN, { 'role' : 'tooltip' }, tipLabel); - var element = goog.dom.createDom(goog.dom.TagName.DIV, { - 'class': className + ' ' + ol.css.CLASS_UNSELECTABLE - }); var button = goog.dom.createDom(goog.dom.TagName.BUTTON, { 'class': 'ol-has-tooltip' }); goog.dom.appendChild(button, tip); - goog.dom.appendChild(element, button); var buttonHandler = new ol.pointer.PointerEventHandler(button); this.registerDisposable(buttonHandler); @@ -60,6 +56,10 @@ ol.control.ZoomToExtent = function(opt_options) { this.blur(); }, false); + var cssClasses = className + ' ' + ol.css.CLASS_UNSELECTABLE + ' ' + + ol.css.CLASS_CONTROL; + var element = goog.dom.createDom(goog.dom.TagName.DIV, cssClasses, button); + goog.base(this, { element: element, target: options.target diff --git a/src/ol/css.js b/src/ol/css.js index 80eb7b523d..dd7040d8f0 100644 --- a/src/ol/css.js +++ b/src/ol/css.js @@ -17,3 +17,12 @@ ol.css.CLASS_UNSELECTABLE = 'ol-unselectable'; * @type {string} */ ol.css.CLASS_UNSUPPORTED = 'ol-unsupported'; + + +/** + * The CSS class for controls. + * + * @const + * @type {string} + */ +ol.css.CLASS_CONTROL = 'ol-control';