# Source: src/ol/control/zoomslidercontrol.js

    // FIXME works for View2D only
    // FIXME should possibly show tooltip when dragging?

    goog.provide('ol.control.ZoomSlider');

    goog.require('goog.asserts');
    goog.require('goog.dom');
    goog.require('goog.dom.TagName');
    goog.require('goog.events');
    goog.require('goog.events.Event');
    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');
    goog.require('ol.animation');
    goog.require('ol.control.Control');
    goog.require('ol.css');
    goog.require('ol.easing');

    /**
     * @classdesc
     * A slider type of control for zooming.
     *
     * Example:
     *
     *     map.addControl(new ol.control.ZoomSlider());
     *
     * @constructor
     * @extends {ol.control.Control}
     * @param {olx.control.ZoomSliderOptions=} opt_options Zoom slider options.
     * @todo api
     */
    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.events.listen(thumbElement, goog.events.EventType.CLICK,
          goog.events.Event.stopPropagation);

      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.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.ZOOMSLIDER_ANIMATION_DURATION,
          easing: ol.easing.easeOut
        }));
        resolution = view.constrainResolution(this.currentResolution_);
        view.setResolution(resolution);
      }
    };