diff --git a/css/ol.css b/css/ol.css index 5dc3fe2d68..f107a5ebd3 100644 --- a/css/ol.css +++ b/css/ol.css @@ -88,6 +88,7 @@ .ol-zoom, .ol-zoom-extent, +.ol-rotate, .ol-full-screen { position: absolute; background-color: #eee; @@ -97,6 +98,7 @@ } .ol-zoom:hover, .ol-zoom-extent:hover, +.ol-rotate:hover, .ol-full-screen:hover { background-color: rgba(255,255,255,0.6); } @@ -104,6 +106,11 @@ top: .5em; left: .5em; } +.ol-rotate { + top: .5em; + right: .5em; + transition: opacity .25s; +} .ol-zoom-extent { top: 4.643em; left: .5em; @@ -122,6 +129,7 @@ .ol-zoom button, .ol-zoom-extent button, +.ol-rotate button, .ol-full-screen button { display: block; margin: 1px; @@ -137,9 +145,11 @@ 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-rotate button::-moz-focus-inner, .ol-full-screen button::-moz-focus-inner { border: none; padding: 0; @@ -147,8 +157,15 @@ .ol-zoom-extent button { line-height: 1.4em; } +.ol-compass { + display: block; + font-family: Arial; + font-weight: normal; + font-size: 1.2em; +} .ol-touch .ol-zoom button, .ol-touch .ol-full-screen button, +.ol-touch .ol-rotate button, .ol-touch .ol-zoom-extent button { font-size: 1.5em; } @@ -159,6 +176,8 @@ .ol-zoom button:focus, .ol-zoom-extent button:hover, .ol-zoom-extent button:focus, +.ol-rotate button:hover, +.ol-rotate button:focus, .ol-full-screen button:hover, .ol-full-screen button:focus { text-decoration: none; @@ -168,10 +187,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 +247,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; diff --git a/externs/olx.js b/externs/olx.js index c939b81be9..bfc763a8e9 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -875,6 +875,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/rotatecontrol.js b/src/ol/control/rotatecontrol.js new file mode 100644 index 0000000000..d5f4a6ef1c --- /dev/null +++ b/src/ol/control/rotatecontrol.js @@ -0,0 +1,138 @@ +// 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; + 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; +};