// FIXME works for View2D only // FIXME should possibly show tooltip when dragging? goog.provide('ol.control.ZoomSlider'); goog.require('goog.array'); goog.require('goog.asserts'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.fx.Dragger'); goog.require('goog.fx.Dragger.EventType'); goog.require('goog.math'); goog.require('goog.math.Rect'); goog.require('goog.style'); goog.require('ol.animation'); goog.require('ol.control.Control'); goog.require('ol.css'); goog.require('ol.easing'); /** * @define {number} Animation duration. */ ol.control.ZOOMSLIDER_ANIMATION_DURATION = 200; /** * A slider type of control for zooming. * * Example: * * map.addControl(new ol.control.ZoomSlider()); * * @constructor * @extends {ol.control.Control} * @param {ol.control.ZoomSliderOptions=} opt_options Zoom slider options. * @todo stability experimental */ ol.control.ZoomSlider = function(opt_options) { var options = goog.isDef(opt_options) ? opt_options : {}; /** * Will hold the current resolution of the view. * * @type {number|undefined} * @private */ this.currentResolution_ = undefined; /** * The direction of the slider. Will be determined from actual display of the * container and defaults to ol.control.ZoomSlider.direction.VERTICAL. * * @type {ol.control.ZoomSlider.direction} * @private */ this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; /** * Whether the slider is initialized. * @type {boolean} * @private */ this.sliderInitialized_ = false; var className = goog.isDef(options.className) ? options.className : 'ol-zoomslider'; var thumbElement = goog.dom.createDom(goog.dom.TagName.DIV, [className + '-thumb', ol.css.CLASS_UNSELECTABLE]); var sliderElement = goog.dom.createDom(goog.dom.TagName.DIV, [className, ol.css.CLASS_UNSELECTABLE], thumbElement); /** * @type {goog.fx.Dragger} * @private */ this.dragger_ = new goog.fx.Dragger(thumbElement); this.registerDisposable(this.dragger_); goog.events.listen(this.dragger_, [ goog.fx.Dragger.EventType.DRAG, goog.fx.Dragger.EventType.END ], this.handleSliderChange_, undefined, this); goog.events.listen(sliderElement, goog.events.EventType.CLICK, this.handleContainerClick_, false, this); goog.base(this, { element: sliderElement }); }; goog.inherits(ol.control.ZoomSlider, ol.control.Control); /** * The enum for available directions. * * @enum {number} */ ol.control.ZoomSlider.direction = { VERTICAL: 0, HORIZONTAL: 1 }; /** * @inheritDoc */ ol.control.ZoomSlider.prototype.setMap = function(map) { goog.base(this, 'setMap', map); if (!goog.isNull(map)) { map.render(); } }; /** * Initializes the slider element. This will determine and set this controls * direction_ and also constrain the dragging of the thumb to always be within * the bounds of the container. * * @private */ ol.control.ZoomSlider.prototype.initSlider_ = function() { var container = this.element, thumb = goog.dom.getFirstElementChild(container), elemSize = goog.style.getContentBoxSize(container), thumbBounds = goog.style.getBounds(thumb), thumbMargins = goog.style.getMarginBox(thumb), thumbBorderBox = goog.style.getBorderBox(thumb), w = elemSize.width - thumbMargins.left - thumbMargins.right - thumbBorderBox.left - thumbBorderBox.right - thumbBounds.width, h = elemSize.height - thumbMargins.top - thumbMargins.bottom - thumbBorderBox.top - thumbBorderBox.bottom - thumbBounds.height, limits; if (elemSize.width > elemSize.height) { this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL; limits = new goog.math.Rect(0, 0, w, 0); } else { this.direction_ = ol.control.ZoomSlider.direction.VERTICAL; limits = new goog.math.Rect(0, 0, 0, h); } this.dragger_.setLimits(limits); this.sliderInitialized_ = true; }; /** * @inheritDoc */ ol.control.ZoomSlider.prototype.handleMapPostrender = function(mapEvent) { if (goog.isNull(mapEvent.frameState)) { return; } goog.asserts.assert( goog.isDefAndNotNull(mapEvent.frameState.view2DState)); if (!this.sliderInitialized_) { this.initSlider_(); } var res = mapEvent.frameState.view2DState.resolution; if (res !== this.currentResolution_) { this.currentResolution_ = res; this.positionThumbForResolution_(res); } }; /** * @param {goog.events.BrowserEvent} browserEvent The browser event to handle. * @private */ ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) { var map = this.getMap(); var view = map.getView().getView2D(); var resolution; var amountDragged = this.amountDragged_(browserEvent.offsetX, browserEvent.offsetY); resolution = this.resolutionForAmount_(amountDragged); goog.asserts.assert(goog.isDef(resolution)); map.beforeRender(ol.animation.zoom({ resolution: resolution, duration: ol.control.ZOOMSLIDER_ANIMATION_DURATION, easing: ol.easing.easeOut })); resolution = view.constrainResolution(resolution); view.setResolution(resolution); }; /** * Positions the thumb inside its container according to the given resolution. * * @param {number} res The res. * @private */ ol.control.ZoomSlider.prototype.positionThumbForResolution_ = function(res) { var amount = this.amountForResolution_(res), dragger = this.dragger_, thumb = goog.dom.getFirstElementChild(this.element); if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) { var left = dragger.limits.left + dragger.limits.width * amount; goog.style.setPosition(thumb, left); } else { var top = dragger.limits.top + dragger.limits.height * amount; goog.style.setPosition(thumb, dragger.limits.left, top); } }; /** * Calculates the amount the thumb has been dragged to allow for calculation * of the corresponding resolution. * * @param {number} x Pixel position relative to the left of the slider. * @param {number} y Pixel position relative to the top of the slider. * @return {number} The amount the thumb has been dragged. * @private */ ol.control.ZoomSlider.prototype.amountDragged_ = function(x, y) { var draggerLimits = this.dragger_.limits, amount = 0; if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) { amount = (x - draggerLimits.left) / draggerLimits.width; } else { amount = (y - draggerLimits.top) / draggerLimits.height; } return amount; }; /** * Calculates the corresponding resolution of the thumb by the amount it has * been dragged from its minimum. * * @param {number} amount The amount the thumb has been dragged. * @return {number} The corresponding resolution. * @private */ ol.control.ZoomSlider.prototype.resolutionForAmount_ = function(amount) { // FIXME do we really need this affine transform? amount = (goog.math.clamp(amount, 0, 1) - 1) * -1; var fn = this.getMap().getView().getView2D().getResolutionForValueFunction(); return fn(amount); }; /** * Determines an amount of dragging relative to this minimum position by the * given resolution. * * @param {number} res The resolution to get the amount for. * @return {number} The corresponding value (between 0 and 1). * @private */ ol.control.ZoomSlider.prototype.amountForResolution_ = function(res) { var fn = this.getMap().getView().getView2D().getValueForResolutionFunction(); var value = fn(res); // FIXME do we really need this affine transform? return (value - 1) * -1; }; /** * Handles the user caused changes of the slider thumb and adjusts the * resolution of our map accordingly. Will be called both while dragging and * when dragging ends. * * @param {goog.fx.DragDropEvent} e The dragdropevent. * @private */ ol.control.ZoomSlider.prototype.handleSliderChange_ = function(e) { var map = this.getMap(); var view = map.getView().getView2D(); var resolution; if (e.type === goog.fx.Dragger.EventType.DRAG) { var amountDragged = this.amountDragged_(e.left, e.top); resolution = this.resolutionForAmount_(amountDragged); if (resolution !== this.currentResolution_) { this.currentResolution_ = resolution; view.setResolution(resolution); } } else { goog.asserts.assert(goog.isDef(this.currentResolution_)); map.beforeRender(ol.animation.zoom({ resolution: this.currentResolution_, duration: ol.control.ZOOMSLIDER_ANIMATION_DURATION, easing: ol.easing.easeOut })); resolution = view.constrainResolution(this.currentResolution_); view.setResolution(resolution); } };