Source: Box.js

//////////////////////////////////////////////////////////////////////////////
// CGview Box
//////////////////////////////////////////////////////////////////////////////

import Position from './Position';
import Anchor from './Anchor';
import utils from './Utils';

/**
 * A Box consists of an x and y point (the top-left corner) and
 * a width and height. The Box position can be relative to the
 * canvas where the position stays static or to the map in which
 * case the position moves with the map.
 *
 * <a name="attributes"></a>
 * ### Attributes
 *
 * Attribute             | Type          | Description
 * ----------------------|---------------|------------
 * [width](#width)       | Number        | Width of box (Default: 100)
 * [height](#height)     | Number        | Height of box (Default: 100)
 * [padding](#padding)   | Number        | Sets paddedX and paddedY values (Default: 0)
 * [position](#position) | String\|Object | Where to place the box. See {@link Position} for details.
 * [anchor](#anchor)     | String\|Object | Where the position should be anchored to the box.
 * [color](#color)       | String\|Color  | A string describing the color. See {@link Color} for details. (DOESN'T DO ANYTHING YET)
 *
 * Position:
 * If the position is on (i.e. relativeTo) the 'canvas', the box will be in a static position
 * and will not move as the map is panned. String values (e.g. top-right, bottom-middle, etc)
 * position the box appropriately. An object with xPercent and yPercent values between
 * 0 and 100 will position the box along the x and y axes starting from the top-left.
 * The string values are associated with specific offsets. For example,
 *   - top-left = {xPercent: 0, yPercent: 0}
 *   - middle-center = {xPercent: 50, yPercent: 50}
 *   - bottom-right = {xPercent: 100, yPercent: 100}
 *
 * If position is on (i.e. relativeTo) the 'map', the box will move with the map as it's panned.
 * The position will consist of
 *   - lengthPercent: 0 - start of map; 50 - middle of map; 100 - end of map
 *   - mapOffset or bbOffsetPercent: distance from the backbone
 *
 * ### Examples
 *
 */
class Box {

  /**
   * Create a Box.
   * @param {Viewer} viewer - The viewer this box will be associated with.
   * @param {Object} options - [Attributes](#attributes) used to create the box.
   */
  constructor(viewer, options = {}) {
    this._viewer = viewer;
    this._width = utils.defaultFor(options.width, 100);
    this._height = utils.defaultFor(options.height, 100);
    this.anchor = options.anchor;
    // Set position after anchor. If position is on canvas, the anchor will be updated.
    this.position = utils.defaultFor(options.position, 'middle-center');
    this.padding = utils.defaultFor(options.padding, 0);
    this.color = utils.defaultFor(options.color, 'white');
  }

  /**
   * Return the class name as a string.
   * @return {String} - 'Box'
   */
  toString() {
    return 'Box';
  }

  get viewer() {
    return this._viewer;
  }

  get canvas() {
    return this.viewer.canvas;
  }

  /**
   * Alias for [Position on](Position.html#on). Values: 'map', 'campus'.
   */
  get on() {
    return this.position.on;
  }

  set on(value) {
    this.position.on = value;
    // this._adjustAnchor(); // Only needed when onCanvas, which is called when the position is set
    if (this.position.onMap) {
      // To keep the box in the same position when changing to onMap, the auto anchor has to be turned off.
      this.anchor.auto = false;
    } else if (this.position.onCanvas) {
      // To keep the box in the same position when changing to onCanvas, the position must be adjusted.
      // Adjust Position
      this.position = {
        xPercent: this.x / (this.viewer.width - this.width) * 100,
        yPercent: this.y / (this.viewer.height - this.height) * 100
      }
    }
  }

  /**
   * @member {String} - Get or set the postion. String values include: "top-left", "top-center", "top-right", "middle-left", "middle-center", "middle-right", "bottom-left", "bottom-center", or "bottom-right".
   */
  get position() {
    return this._position;
  }

  set position(value) {
    this._position = new Position(this.viewer, value);
    this._adjustAnchor();
    this.refresh(true);
  }

  /**
   * @member {String|Object} - Get or set the anchor.
   */
  get anchor() {
    return this._anchor;
  }

  set anchor(value) {
    if (this.position && this.position.onCanvas) { return; }
    this._anchor = new Anchor(value);
    this.position && this.refresh(true);
  }

  /**
   * @member {Number} - Get or set the width.
   */
  get width() {
    return this._width;
  }

  set width(value) {
    this._width = value;
    this.refresh(true);
  }

  /**
   * @member {Number} - Get or set the height.
   */
  get height() {
    return this._height;
  }

  set height(value) {
    this._height = value;
    this.refresh(true);
  }

  /**
   * @member {Number} - Get the x position of the origin.
   */
  get x() {
    return this._x;
  }

  /**
   * @member {Number} - Get the y position of the origin.
   */
  get y() {
    return this._y;
  }

  /**
   * @member {Number} - Get or set the padding. This will be added to x and y when accessed via paddedX and paddedY.
   */
  get padding() {
    return this._padding;
  }

  set padding(value) {
    this._padding = value;
  }

  /**
   * @member {Number} - Get the x position of the origin plus padding.
   */
  get paddedX() {
    return this.x + this.padding;
  }

  /**
   * @member {Number} - Get the y position of the origin plus padding.
   */
  get paddedY() {
    return this.y + this.padding;
  }

  /**
   * @member {Number} - Get bottom of the Box
   */
  get bottom() {
    return this.y + this.height;
  }

  /**
   * @member {Number} - Get bottom of the Box minus padding
   */
  get bottomPadded() {
    return this.bottom - this.padding;
  }

  /**
   * @member {Number} - Get top of the Box. Same as Y.
   */
  get top() {
    return this.y;
  }

  /**
   * @member {Number} - Get top of the Box plus padding.
   */
  get topPadded() {
    return this.top + this.padding;
  }

  /**
   * @member {Number} - Get left of the Box. Same as X.
   */
  get left() {
    return this.x;
  }

  /**
   * @member {Number} - Get left of the Box plus padding.
   */
  get leftPadded() {
    return this.left + this.padding;
  }

  /**
   * @member {Number} - Get right of the Box.
   */
  get right() {
    return this.x + this.width;
  }

  /**
   * @member {Number} - Get right of the Box minus padding.
   */
  get rightPadded() {
    return this.right - this.padding;
  }

  /**
   * @member {Number} - Get the center x of the box.
   */
  get centerX() {
    return this.x + (this.width / 2);
  }

  /**
   * @member {Number} - Get the center y of the box.
   */
  get centerY() {
    return this.y + (this.height / 2);
  }

  resize(width, height) {
    this._width = width;
    this._height = height;
    this.refresh(true);
  }

  /**
   * Check if the Box conains the point
   *
   * @param {Number} x - X coordinate of the point
   * @param {Number} y - Y coordinate of the point
   * @return {Boolean}
   */
  containsPt(x, y) {
    return ( x >= this.x && x <= (this.x + this.width) && y >= this.y && y <= (this.y + this.height) );
  }

  _adjustAnchor() {
    if (this.position.onCanvas) {
      this.anchor.xPercent = this.position.xPercent;
      this.anchor.yPercent = this.position.yPercent;
      this.anchor.auto = true;
    }
  }

  refresh(force = false) {
    if (!force && this.on === 'canvas') { return; }
    this.position.refresh();
    if (this.anchor.auto) {
      this.anchor.autoUpdateForPosition(this.position);
    }
    this._x = this.position.x - (this.width * this.anchor.xPercent / 100);
    this._y = this.position.y - (this.height * this.anchor.yPercent / 100);
  }

  /**
   * Clear the rect area described by this box using the provided context.
   * @param {Context}  ctx - Context used to clear the rect.
   */
  clear(ctx) {
    // Added margin of 1 to remove thin lines of previous background that were not being removed
    ctx.clearRect(this.x - 1, this.y - 1, this.width + 2, this.height + 2);
  }

}

export default Box;