index.js

const Button = require('./button');
const Range = require('./controls/range');
const Calc = require('./util/calc');

class VariaBoard {

  /**
   * Create a VariaBoard control panel
   *
   * @param {object} config - Configuration object
   * @param {object|string} [config.container=document.body] - DOM element or CSS selector
   * @param {string} [config.title="Control Panel"] - Title of the panel
   *
   * @requires {@link Button}
   * @requires {@link Range}
   * @requires {@link Calc}
   */

  constructor(config) {
    this.namespace = 'variaboard';
    this.controls = {};
    this.buttons = {};

    this.mouse = {
      down: false,
      x: 0,
      y: 0,
      anchor: {
        x: 0,
        y: 0
      }
    }

    this.needsUpdate = false;
    this.raf = null;

    this.container = config.container !== undefined ? config.container : document.body;
    this.title = config.title !== undefined ? config.title : 'VariaBoard';
    
    this.createDOM();
    this.listen();
  }

  /**
   * Create necessary DOM elements
   */

  createDOM() {
    this.dom = {};

    // container
    this.dom.container = this.container;

    // panel
    this.dom.panel = document.createElement('div');
    this.dom.panel.classList.add(`${this.namespace}-panel`);

    // title
    this.dom.title = document.createElement('h1');
    this.dom.title.classList.add(`${this.namespace}-title`);
    this.dom.title.textContent = this.title;
    this.dom.panel.appendChild(this.dom.title);

    // controls
    this.dom.controls = document.createElement('div');
    this.dom.controls.classList.add(`${this.namespace}-controls`);
    this.dom.panel.appendChild(this.dom.controls);

    // add panel to container
    this.dom.container.appendChild(this.dom.panel);
  }

  /**
   * Setup event listeners
   */

  listen() {
    window.addEventListener('mouseup', (e) => this.onWindowMouseup(e));
    window.addEventListener('mousemove', (e) => this.onWindowMousemove(e));
  }

  /**
   * On window mouse up event
   *
   * @param {object} e - Event object
   */

  onWindowMouseup(e) {
    this.mouse.down = false;
    for(let key in this.controls) {
      let control = this.controls[key];
      control.onWindowMouseup(e);
    }
  }

  /**
   * On window mouse move event
   *
   * @param {object} e - Event object
   */

  onWindowMousemove(e) {
    this.mouse.x = e.clientX;
    this.mouse.y = e.clientY;
    for(let key in this.controls) {
      let control = this.controls[key];
      control.onWindowMousemove(e);
    }
  }

  /**
   * Update based on requestAnimationFrame()
   */

  update() {
    this.needsUpdate = false;
    for(let key in this.controls) {
      if(this.controls[key].type === 'range') {
        this.controls[key].easeSet();
        if(!this.controls[key].settled) {
          this.needsUpdate = true;
        }
      }
    }

    if(this.needsUpdate) {
      this.raf = requestAnimationFrame(() => this.update());
    }
  }

  /**
   * Add a button
   *
   * @param {object} config - Configuration object
   * @param {object} config.id - ID slug
   * @param {object} config.title - Title text
   * @param {object} [config.callback=() => {}] - Callback function for button press
   */

  addButton(config) {
    this.buttons[config.id] = new Button(this, config);
  }

  /**
   * Add a range control via {@linkcode Range}
   *
   * @param {object} config - Configuration object
   * @param {string} config.id - Unique id/slug
   * @param {string} config.title - UI display title
   * @param {number} config.min - Minimum value
   * @param {number} config.max - Maximum value
   * @param {number} config.step - Step size
   * @param {number} config.default - Starting value
   * @param {boolean} config.randomizable - Can be randomized individually and by randomizing all
   * @param {boolean} config.mutable - Can be mutated individually and by mutating all
   * @param {boolean} config.locked - Temporarily toggle whether the control is affected by randomization and mutation
   */

  addRange(config) {
    let control = new Range(this, config);
    this.controls[control.id] = control;
  }

  get(id) {
    return this.controls[id].value;
  }

  randomize() {
    for(let key in this.controls) {
      this.controls[key].randomize();
    }
    cancelAnimationFrame(this.raf);
    this.update();
  }

  mutate() {
    for(let key in this.controls) {
      let control = this.controls[key];
      let size = (control.max - control.min) / 15;
      control.settled = false;
      if(control.value <= control.min) {
        control.valueTarget = Calc.rand(control.step, control.min + size);
      } else if(control.value >= control.max) {
        control.valueTarget = Calc.rand(control.max - size, control.max - control.step);
      } else {
        control.valueTarget = control.value + Calc.rand(-size, size);
      }
      cancelAnimationFrame(this.raf);
      this.update();
    }
  }

}

module.exports = VariaBoard;