BVB Source Codes

dat.gui Show GUI.js Source code

Return Download dat.gui: download GUI.js Source code - Download dat.gui Source code - Type:.js
  1. /**
  2.  * dat-gui JavaScript Controller Library
  3.  * http://code.google.com/p/dat-gui
  4.  *
  5.  * Copyright 2011 Data Arts Team, Google Creative Lab
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  * http://www.apache.org/licenses/LICENSE-2.0
  12.  */
  13.  
  14. import css from '../utils/css';
  15. import saveDialogueContents from './saveDialogue.html';
  16. import ControllerFactory from '../controllers/ControllerFactory';
  17. import Controller from '../controllers/Controller';
  18. import BooleanController from '../controllers/BooleanController';
  19. import FunctionController from '../controllers/FunctionController';
  20. import NumberControllerBox from '../controllers/NumberControllerBox';
  21. import NumberControllerSlider from '../controllers/NumberControllerSlider';
  22. import ColorController from '../controllers/ColorController';
  23. import requestAnimationFrame from '../utils/requestAnimationFrame';
  24. import CenteredDiv from '../dom/CenteredDiv';
  25. import dom from '../dom/dom';
  26. import common from '../utils/common';
  27.  
  28. import styleSheet from './style.scss'; // CSS to embed in build
  29.  
  30. css.inject(styleSheet);
  31.  
  32. /** Outer-most className for GUI's */
  33. const CSS_NAMESPACE = 'dg';
  34.  
  35. const HIDE_KEY_CODE = 72;
  36.  
  37. /** The only value shared between the JS and SCSS. Use caution. */
  38. const CLOSE_BUTTON_HEIGHT = 20;
  39.  
  40. const DEFAULT_DEFAULT_PRESET_NAME = 'Default';
  41.  
  42. const SUPPORTS_LOCAL_STORAGE = (function() {
  43.   try {
  44.     return 'localStorage' in window && window.localStorage !== null;
  45.   } catch (e) {
  46.     return false;
  47.   }
  48. }());
  49.  
  50. let SAVE_DIALOGUE;
  51.  
  52. /** Have we yet to create an autoPlace GUI? */
  53. let autoPlaceVirgin = true;
  54.  
  55. /** Fixed position div that auto place GUI's go inside */
  56. let autoPlaceContainer;
  57.  
  58. /** Are we hiding the GUI's ? */
  59. let hide = false;
  60.  
  61. /** GUI's which should be hidden */
  62. const hideableGuis = [];
  63.  
  64. /**
  65.  * A lightweight controller library for JavaScript. It allows you to easily
  66.  * manipulate variables and fire functions on the fly.
  67.  * @class
  68.  *
  69.  * @member dat.gui
  70.  *
  71.  * @param {Object} [params]
  72.  * @param {String} [params.name] The name of this GUI.
  73.  * @param {Object} [params.load] JSON object representing the saved state of
  74.  * this GUI.
  75.  * @param {Boolean} [params.auto=true]
  76.  * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in.
  77.  * @param {Boolean} [params.closed] If true, starts closed
  78.  */
  79. const GUI = function(pars) {
  80.   const _this = this;
  81.  
  82.   let params = pars || {};
  83.  
  84.   /**
  85.    * Outermost DOM Element
  86.    * @type DOMElement
  87.    */
  88.   this.domElement = document.createElement('div');
  89.   this.__ul = document.createElement('ul');
  90.   this.domElement.appendChild(this.__ul);
  91.  
  92.   dom.addClass(this.domElement, CSS_NAMESPACE);
  93.  
  94.   /**
  95.    * Nested GUI's by name
  96.    * @ignore
  97.    */
  98.   this.__folders = {};
  99.  
  100.   this.__controllers = [];
  101.  
  102.   /**
  103.    * List of objects I'm remembering for save, only used in top level GUI
  104.    * @ignore
  105.    */
  106.   this.__rememberedObjects = [];
  107.  
  108.   /**
  109.    * Maps the index of remembered objects to a map of controllers, only used
  110.    * in top level GUI.
  111.    *
  112.    * @private
  113.    * @ignore
  114.    *
  115.    * @example
  116.    * [
  117.    *  {
  118.      *    propertyName: Controller,
  119.      *    anotherPropertyName: Controller
  120.      *  },
  121.    *  {
  122.      *    propertyName: Controller
  123.      *  }
  124.    * ]
  125.    */
  126.   this.__rememberedObjectIndecesToControllers = [];
  127.  
  128.   this.__listening = [];
  129.  
  130.   // Default parameters
  131.   params = common.defaults(params, {
  132.     autoPlace: true,
  133.     width: GUI.DEFAULT_WIDTH
  134.   });
  135.  
  136.   params = common.defaults(params, {
  137.     resizable: params.autoPlace,
  138.     hideable: params.autoPlace
  139.   });
  140.  
  141.   if (!common.isUndefined(params.load)) {
  142.     // Explicit preset
  143.     if (params.preset) {
  144.       params.load.preset = params.preset;
  145.     }
  146.   } else {
  147.     params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME };
  148.   }
  149.  
  150.   if (common.isUndefined(params.parent) && params.hideable) {
  151.     hideableGuis.push(this);
  152.   }
  153.  
  154.   // Only root level GUI's are resizable.
  155.   params.resizable = common.isUndefined(params.parent) && params.resizable;
  156.  
  157.   if (params.autoPlace && common.isUndefined(params.scrollable)) {
  158.     params.scrollable = true;
  159.   }
  160. //    params.scrollable = common.isUndefined(params.parent) && params.scrollable === true;
  161.  
  162.   // Not part of params because I don't want people passing this in via
  163.   // constructor. Should be a 'remembered' value.
  164.   let useLocalStorage =
  165.     SUPPORTS_LOCAL_STORAGE &&
  166.     localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true';
  167.  
  168.   let saveToLocalStorage;
  169.  
  170.   Object.defineProperties(this,
  171.     /** @lends dat.gui.GUI.prototype */
  172.     {
  173.       /**
  174.        * The parent <code>GUI</code>
  175.        * @type dat.gui.GUI
  176.        */
  177.       parent: {
  178.         get: function() {
  179.           return params.parent;
  180.         }
  181.       },
  182.  
  183.       scrollable: {
  184.         get: function() {
  185.           return params.scrollable;
  186.         }
  187.       },
  188.  
  189.       /**
  190.        * Handles <code>GUI</code>'s element placement for you
  191.        * @type Boolean
  192.        */
  193.       autoPlace: {
  194.         get: function() {
  195.           return params.autoPlace;
  196.         }
  197.       },
  198.  
  199.       /**
  200.        * The identifier for a set of saved values
  201.        * @type String
  202.        */
  203.       preset: {
  204.         get: function() {
  205.           if (_this.parent) {
  206.             return _this.getRoot().preset;
  207.           }
  208.  
  209.           return params.load.preset;
  210.         },
  211.  
  212.         set: function(v) {
  213.           if (_this.parent) {
  214.             _this.getRoot().preset = v;
  215.           } else {
  216.             params.load.preset = v;
  217.           }
  218.           setPresetSelectIndex(this);
  219.           _this.revert();
  220.         }
  221.       },
  222.  
  223.       /**
  224.        * The width of <code>GUI</code> element
  225.        * @type Number
  226.        */
  227.       width: {
  228.         get: function() {
  229.           return params.width;
  230.         },
  231.         set: function(v) {
  232.           params.width = v;
  233.           setWidth(_this, v);
  234.         }
  235.       },
  236.  
  237.       /**
  238.        * The name of <code>GUI</code>. Used for folders. i.e
  239.        * a folder's name
  240.        * @type String
  241.        */
  242.       name: {
  243.         get: function() {
  244.           return params.name;
  245.         },
  246.         set: function(v) {
  247.           // TODO Check for collisions among sibling folders
  248.           params.name = v;
  249.           if (titleRowName) {
  250.             titleRowName.innerHTML = params.name;
  251.           }
  252.         }
  253.       },
  254.  
  255.       /**
  256.        * Whether the <code>GUI</code> is collapsed or not
  257.        * @type Boolean
  258.        */
  259.       closed: {
  260.         get: function() {
  261.           return params.closed;
  262.         },
  263.         set: function(v) {
  264.           params.closed = v;
  265.           if (params.closed) {
  266.             dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
  267.           } else {
  268.             dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
  269.           }
  270.           // For browsers that aren't going to respect the CSS transition,
  271.           // Lets just check our height against the window height right off
  272.           // the bat.
  273.           this.onResize();
  274.  
  275.           if (_this.__closeButton) {
  276.             _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED;
  277.           }
  278.         }
  279.       },
  280.  
  281.       /**
  282.        * Contains all presets
  283.        * @type Object
  284.        */
  285.       load: {
  286.         get: function() {
  287.           return params.load;
  288.         }
  289.       },
  290.  
  291.       /**
  292.        * Determines whether or not to use <a href="https://developer.mozilla.org/en/DOM/Storage#localStorage">localStorage</a> as the means for
  293.        * <code>remember</code>ing
  294.        * @type Boolean
  295.        */
  296.       useLocalStorage: {
  297.  
  298.         get: function() {
  299.           return useLocalStorage;
  300.         },
  301.         set: function(bool) {
  302.           if (SUPPORTS_LOCAL_STORAGE) {
  303.             useLocalStorage = bool;
  304.             if (bool) {
  305.               dom.bind(window, 'unload', saveToLocalStorage);
  306.             } else {
  307.               dom.unbind(window, 'unload', saveToLocalStorage);
  308.             }
  309.             localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool);
  310.           }
  311.         }
  312.       }
  313.     });
  314.  
  315.   // Are we a root level GUI?
  316.   if (common.isUndefined(params.parent)) {
  317.     params.closed = false;
  318.  
  319.     dom.addClass(this.domElement, GUI.CLASS_MAIN);
  320.     dom.makeSelectable(this.domElement, false);
  321.  
  322.     // Are we supposed to be loading locally?
  323.     if (SUPPORTS_LOCAL_STORAGE) {
  324.       if (useLocalStorage) {
  325.         _this.useLocalStorage = true;
  326.  
  327.         const savedGui = localStorage.getItem(getLocalStorageHash(this, 'gui'));
  328.  
  329.         if (savedGui) {
  330.           params.load = JSON.parse(savedGui);
  331.         }
  332.       }
  333.     }
  334.  
  335.     this.__closeButton = document.createElement('div');
  336.     this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
  337.     dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
  338.     this.domElement.appendChild(this.__closeButton);
  339.  
  340.     dom.bind(this.__closeButton, 'click', function() {
  341.       _this.closed = !_this.closed;
  342.     });
  343.     // Oh, you're a nested GUI!
  344.   } else {
  345.     if (params.closed === undefined) {
  346.       params.closed = true;
  347.     }
  348.  
  349.     const titleRowName = document.createTextNode(params.name);
  350.     dom.addClass(titleRowName, 'controller-name');
  351.  
  352.     const titleRow = addRow(_this, titleRowName);
  353.  
  354.     const onClickTitle = function(e) {
  355.       e.preventDefault();
  356.       _this.closed = !_this.closed;
  357.       return false;
  358.     };
  359.  
  360.     dom.addClass(this.__ul, GUI.CLASS_CLOSED);
  361.  
  362.     dom.addClass(titleRow, 'title');
  363.     dom.bind(titleRow, 'click', onClickTitle);
  364.  
  365.     if (!params.closed) {
  366.       this.closed = false;
  367.     }
  368.   }
  369.  
  370.   if (params.autoPlace) {
  371.     if (common.isUndefined(params.parent)) {
  372.       if (autoPlaceVirgin) {
  373.         autoPlaceContainer = document.createElement('div');
  374.         dom.addClass(autoPlaceContainer, CSS_NAMESPACE);
  375.         dom.addClass(autoPlaceContainer, GUI.CLASS_AUTO_PLACE_CONTAINER);
  376.         document.body.appendChild(autoPlaceContainer);
  377.         autoPlaceVirgin = false;
  378.       }
  379.  
  380.       // Put it in the dom for you.
  381.       autoPlaceContainer.appendChild(this.domElement);
  382.  
  383.       // Apply the auto styles
  384.       dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
  385.     }
  386.  
  387.  
  388.     // Make it not elastic.
  389.     if (!this.parent) {
  390.       setWidth(_this, params.width);
  391.     }
  392.   }
  393.  
  394.   this.__resizeHandler = function() {
  395.     _this.onResizeDebounced();
  396.   };
  397.  
  398.   dom.bind(window, 'resize', this.__resizeHandler);
  399.   dom.bind(this.__ul, 'webkitTransitionEnd', this.__resizeHandler);
  400.   dom.bind(this.__ul, 'transitionend', this.__resizeHandler);
  401.   dom.bind(this.__ul, 'oTransitionEnd', this.__resizeHandler);
  402.   this.onResize();
  403.  
  404.   if (params.resizable) {
  405.     addResizeHandle(this);
  406.   }
  407.  
  408.   saveToLocalStorage = function() {
  409.     if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') {
  410.       localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject()));
  411.     }
  412.   };
  413.  
  414.   // expose this method publicly
  415.   this.saveToLocalStorageIfPossible = saveToLocalStorage;
  416.  
  417.   function resetWidth() {
  418.     const root = _this.getRoot();
  419.     root.width += 1;
  420.     common.defer(function() {
  421.       root.width -= 1;
  422.     });
  423.   }
  424.  
  425.   if (!params.parent) {
  426.     resetWidth();
  427.   }
  428. };
  429.  
  430. GUI.toggleHide = function() {
  431.   hide = !hide;
  432.   common.each(hideableGuis, function(gui) {
  433.     gui.domElement.style.display = hide ? 'none' : '';
  434.   });
  435. };
  436.  
  437. GUI.CLASS_AUTO_PLACE = 'a';
  438. GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac';
  439. GUI.CLASS_MAIN = 'main';
  440. GUI.CLASS_CONTROLLER_ROW = 'cr';
  441. GUI.CLASS_TOO_TALL = 'taller-than-window';
  442. GUI.CLASS_CLOSED = 'closed';
  443. GUI.CLASS_CLOSE_BUTTON = 'close-button';
  444. GUI.CLASS_DRAG = 'drag';
  445.  
  446. GUI.DEFAULT_WIDTH = 245;
  447. GUI.TEXT_CLOSED = 'Close Controls';
  448. GUI.TEXT_OPEN = 'Open Controls';
  449.  
  450. GUI._keydownHandler = function(e) {
  451.   if (document.activeElement.type !== 'text' &&
  452.     (e.which === HIDE_KEY_CODE || e.keyCode === HIDE_KEY_CODE)) {
  453.     GUI.toggleHide();
  454.   }
  455. };
  456. dom.bind(window, 'keydown', GUI._keydownHandler, false);
  457.  
  458. common.extend(
  459.   GUI.prototype,
  460.  
  461.   /** @lends dat.gui.GUI */
  462.   {
  463.  
  464.     /**
  465.      * @param object
  466.      * @param property
  467.      * @returns {dat.controllers.Controller} The new controller that was added.
  468.      * @instance
  469.      */
  470.     add: function(object, property) {
  471.       return add(
  472.         this,
  473.         object,
  474.         property,
  475.         {
  476.           factoryArgs: Array.prototype.slice.call(arguments, 2)
  477.         }
  478.       );
  479.     },
  480.  
  481.     /**
  482.      * @param object
  483.      * @param property
  484.      * @returns {dat.controllers.ColorController} The new controller that was added.
  485.      * @instance
  486.      */
  487.     addColor: function(object, property) {
  488.       return add(
  489.         this,
  490.         object,
  491.         property,
  492.         {
  493.           color: true
  494.         }
  495.       );
  496.     },
  497.  
  498.     /**
  499.      * @param controller
  500.      * @instance
  501.      */
  502.     remove: function(controller) {
  503.       // TODO listening?
  504.       this.__ul.removeChild(controller.__li);
  505.       this.__controllers.splice(this.__controllers.indexOf(controller), 1);
  506.       const _this = this;
  507.       common.defer(function() {
  508.         _this.onResize();
  509.       });
  510.     },
  511.  
  512.     destroy: function() {
  513.       if (this.autoPlace) {
  514.         autoPlaceContainer.removeChild(this.domElement);
  515.       }
  516.  
  517.       dom.unbind(window, 'keydown', GUI._keydownHandler, false);
  518.       dom.unbind(window, 'resize', this.__resizeHandler);
  519.  
  520.       if (this.saveToLocalStorageIfPossible) {
  521.         dom.unbind(window, 'unload', this.saveToLocalStorageIfPossible);
  522.       }
  523.     },
  524.  
  525.     /**
  526.      * @param name
  527.      * @returns {dat.gui.GUI} The new folder.
  528.      * @throws {Error} if this GUI already has a folder by the specified
  529.      * name
  530.      * @instance
  531.      */
  532.     addFolder: function(name) {
  533.       // We have to prevent collisions on names in order to have a key
  534.       // by which to remember saved values
  535.       if (this.__folders[name] !== undefined) {
  536.         throw new Error('You already have a folder in this GUI by the' +
  537.           ' name "' + name + '"');
  538.       }
  539.  
  540.       const newGuiParams = { name: name, parent: this };
  541.  
  542.       // We need to pass down the autoPlace trait so that we can
  543.       // attach event listeners to open/close folder actions to
  544.       // ensure that a scrollbar appears if the window is too short.
  545.       newGuiParams.autoPlace = this.autoPlace;
  546.  
  547.       // Do we have saved appearance data for this folder?
  548.       if (this.load && // Anything loaded?
  549.         this.load.folders && // Was my parent a dead-end?
  550.         this.load.folders[name]) { // Did daddy remember me?
  551.         // Start me closed if I was closed
  552.         newGuiParams.closed = this.load.folders[name].closed;
  553.  
  554.         // Pass down the loaded data
  555.         newGuiParams.load = this.load.folders[name];
  556.       }
  557.  
  558.       const gui = new GUI(newGuiParams);
  559.       this.__folders[name] = gui;
  560.  
  561.       const li = addRow(this, gui.domElement);
  562.       dom.addClass(li, 'folder');
  563.       return gui;
  564.     },
  565.  
  566.     open: function() {
  567.       this.closed = false;
  568.     },
  569.  
  570.     close: function() {
  571.       this.closed = true;
  572.     },
  573.  
  574.  
  575.     onResize: function() {
  576.       // we debounce this function to prevent performance issues when rotating on tablet/mobile
  577.       const root = this.getRoot();
  578.       if (root.scrollable) {
  579.         const top = dom.getOffset(root.__ul).top;
  580.         let h = 0;
  581.  
  582.         common.each(root.__ul.childNodes, function(node) {
  583.           if (!(root.autoPlace && node === root.__save_row)) {
  584.             h += dom.getHeight(node);
  585.           }
  586.         });
  587.  
  588.         if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) {
  589.           dom.addClass(root.domElement, GUI.CLASS_TOO_TALL);
  590.           root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px';
  591.         } else {
  592.           dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL);
  593.           root.__ul.style.height = 'auto';
  594.         }
  595.       }
  596.  
  597.       if (root.__resize_handle) {
  598.         common.defer(function() {
  599.           root.__resize_handle.style.height = root.__ul.offsetHeight + 'px';
  600.         });
  601.       }
  602.  
  603.       if (root.__closeButton) {
  604.         root.__closeButton.style.width = root.width + 'px';
  605.       }
  606.     },
  607.  
  608.     onResizeDebounced: common.debounce(function() { this.onResize(); }, 200),
  609.  
  610.     /**
  611.      * Mark objects for saving. The order of these objects cannot change as
  612.      * the GUI grows. When remembering new objects, append them to the end
  613.      * of the list.
  614.      *
  615.      * @param {Object...} objects
  616.      * @throws {Error} if not called on a top level GUI.
  617.      * @instance
  618.      */
  619.     remember: function() {
  620.       if (common.isUndefined(SAVE_DIALOGUE)) {
  621.         SAVE_DIALOGUE = new CenteredDiv();
  622.         SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents;
  623.       }
  624.  
  625.       if (this.parent) {
  626.         throw new Error('You can only call remember on a top level GUI.');
  627.       }
  628.  
  629.       const _this = this;
  630.  
  631.       common.each(Array.prototype.slice.call(arguments), function(object) {
  632.         if (_this.__rememberedObjects.length === 0) {
  633.           addSaveMenu(_this);
  634.         }
  635.         if (_this.__rememberedObjects.indexOf(object) === -1) {
  636.           _this.__rememberedObjects.push(object);
  637.         }
  638.       });
  639.  
  640.       if (this.autoPlace) {
  641.         // Set save row width
  642.         setWidth(this, this.width);
  643.       }
  644.     },
  645.  
  646.     /**
  647.      * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI.
  648.      * @instance
  649.      */
  650.     getRoot: function() {
  651.       let gui = this;
  652.       while (gui.parent) {
  653.         gui = gui.parent;
  654.       }
  655.       return gui;
  656.     },
  657.  
  658.     /**
  659.      * @returns {Object} a JSON object representing the current state of
  660.      * this GUI as well as its remembered properties.
  661.      * @instance
  662.      */
  663.     getSaveObject: function() {
  664.       const toReturn = this.load;
  665.       toReturn.closed = this.closed;
  666.  
  667.       // Am I remembering any values?
  668.       if (this.__rememberedObjects.length > 0) {
  669.         toReturn.preset = this.preset;
  670.  
  671.         if (!toReturn.remembered) {
  672.           toReturn.remembered = {};
  673.         }
  674.  
  675.         toReturn.remembered[this.preset] = getCurrentPreset(this);
  676.       }
  677.  
  678.       toReturn.folders = {};
  679.       common.each(this.__folders, function(element, key) {
  680.         toReturn.folders[key] = element.getSaveObject();
  681.       });
  682.  
  683.       return toReturn;
  684.     },
  685.  
  686.     save: function() {
  687.       if (!this.load.remembered) {
  688.         this.load.remembered = {};
  689.       }
  690.  
  691.       this.load.remembered[this.preset] = getCurrentPreset(this);
  692.       markPresetModified(this, false);
  693.       this.saveToLocalStorageIfPossible();
  694.     },
  695.  
  696.     saveAs: function(presetName) {
  697.       if (!this.load.remembered) {
  698.         // Retain default values upon first save
  699.         this.load.remembered = {};
  700.         this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true);
  701.       }
  702.  
  703.       this.load.remembered[presetName] = getCurrentPreset(this);
  704.       this.preset = presetName;
  705.       addPresetOption(this, presetName, true);
  706.       this.saveToLocalStorageIfPossible();
  707.     },
  708.  
  709.     revert: function(gui) {
  710.       common.each(this.__controllers, function(controller) {
  711.         // Make revert work on Default.
  712.         if (!this.getRoot().load.remembered) {
  713.           controller.setValue(controller.initialValue);
  714.         } else {
  715.           recallSavedValue(gui || this.getRoot(), controller);
  716.         }
  717.  
  718.         // fire onFinishChange callback
  719.         if (controller.__onFinishChange) {
  720.           controller.__onFinishChange.call(controller, controller.getValue());
  721.         }
  722.       }, this);
  723.  
  724.       common.each(this.__folders, function(folder) {
  725.         folder.revert(folder);
  726.       });
  727.  
  728.       if (!gui) {
  729.         markPresetModified(this.getRoot(), false);
  730.       }
  731.     },
  732.  
  733.     listen: function(controller) {
  734.       const init = this.__listening.length === 0;
  735.       this.__listening.push(controller);
  736.       if (init) {
  737.         updateDisplays(this.__listening);
  738.       }
  739.     },
  740.  
  741.     updateDisplay: function() {
  742.       common.each(this.__controllers, function(controller) {
  743.         controller.updateDisplay();
  744.       });
  745.       common.each(this.__folders, function(folder) {
  746.         folder.updateDisplay();
  747.       });
  748.     }
  749.   }
  750. );
  751.  
  752. /**
  753.  * Add a row to the end of the GUI or before another row.
  754.  *
  755.  * @param gui
  756.  * @param [newDom] If specified, inserts the dom content in the new row
  757.  * @param [liBefore] If specified, places the new row before another row
  758.  */
  759. function addRow(gui, newDom, liBefore) {
  760.   const li = document.createElement('li');
  761.   if (newDom) {
  762.     li.appendChild(newDom);
  763.   }
  764.  
  765.   if (liBefore) {
  766.     gui.__ul.insertBefore(li, liBefore);
  767.   } else {
  768.     gui.__ul.appendChild(li);
  769.   }
  770.   gui.onResize();
  771.   return li;
  772. }
  773.  
  774. function markPresetModified(gui, modified) {
  775.   const opt = gui.__preset_select[gui.__preset_select.selectedIndex];
  776.  
  777.   // console.log('mark', modified, opt);
  778.   if (modified) {
  779.     opt.innerHTML = opt.value + '*';
  780.   } else {
  781.     opt.innerHTML = opt.value;
  782.   }
  783. }
  784.  
  785. function augmentController(gui, li, controller) {
  786.   controller.__li = li;
  787.   controller.__gui = gui;
  788.  
  789.   common.extend(controller, {
  790.     options: function(options) {
  791.       if (arguments.length > 1) {
  792.         const nextSibling = controller.__li.nextElementSibling;
  793.         controller.remove();
  794.  
  795.         return add(
  796.           gui,
  797.           controller.object,
  798.           controller.property,
  799.           {
  800.             before: nextSibling,
  801.             factoryArgs: [common.toArray(arguments)]
  802.           }
  803.         );
  804.       }
  805.  
  806.       if (common.isArray(options) || common.isObject(options)) {
  807.         const nextSibling = controller.__li.nextElementSibling;
  808.         controller.remove();
  809.  
  810.         return add(
  811.           gui,
  812.           controller.object,
  813.           controller.property,
  814.           {
  815.             before: nextSibling,
  816.             factoryArgs: [options]
  817.           }
  818.         );
  819.       }
  820.     },
  821.  
  822.     name: function(v) {
  823.       controller.__li.firstElementChild.firstElementChild.innerHTML = v;
  824.       return controller;
  825.     },
  826.  
  827.     listen: function() {
  828.       controller.__gui.listen(controller);
  829.       return controller;
  830.     },
  831.  
  832.     remove: function() {
  833.       controller.__gui.remove(controller);
  834.       return controller;
  835.     }
  836.   });
  837.  
  838.   // All sliders should be accompanied by a box.
  839.   if (controller instanceof NumberControllerSlider) {
  840.     const box = new NumberControllerBox(controller.object, controller.property,
  841.       { min: controller.__min, max: controller.__max, step: controller.__step });
  842.  
  843.     common.each(['updateDisplay', 'onChange', 'onFinishChange', 'step'], function(method) {
  844.       const pc = controller[method];
  845.       const pb = box[method];
  846.       controller[method] = box[method] = function() {
  847.         const args = Array.prototype.slice.call(arguments);
  848.         pb.apply(box, args);
  849.         return pc.apply(controller, args);
  850.       };
  851.     });
  852.  
  853.     dom.addClass(li, 'has-slider');
  854.     controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild);
  855.   } else if (controller instanceof NumberControllerBox) {
  856.     const r = function(returned) {
  857.       // Have we defined both boundaries?
  858.       if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) {
  859.         // Well, then lets just replace this with a slider.
  860.  
  861.         // lets remember if the old controller had a specific name or was listening
  862.         const oldName = controller.__li.firstElementChild.firstElementChild.innerHTML;
  863.         const wasListening = controller.__gui.__listening.indexOf(controller) > -1;
  864.  
  865.         controller.remove();
  866.         const newController = add(
  867.           gui,
  868.           controller.object,
  869.           controller.property,
  870.           {
  871.             before: controller.__li.nextElementSibling,
  872.             factoryArgs: [controller.__min, controller.__max, controller.__step]
  873.           });
  874.  
  875.         newController.name(oldName);
  876.         if (wasListening) newController.listen();
  877.  
  878.         return newController;
  879.       }
  880.  
  881.       return returned;
  882.     };
  883.  
  884.     controller.min = common.compose(r, controller.min);
  885.     controller.max = common.compose(r, controller.max);
  886.   } else if (controller instanceof BooleanController) {
  887.     dom.bind(li, 'click', function() {
  888.       dom.fakeEvent(controller.__checkbox, 'click');
  889.     });
  890.  
  891.     dom.bind(controller.__checkbox, 'click', function(e) {
  892.       e.stopPropagation(); // Prevents double-toggle
  893.     });
  894.   } else if (controller instanceof FunctionController) {
  895.     dom.bind(li, 'click', function() {
  896.       dom.fakeEvent(controller.__button, 'click');
  897.     });
  898.  
  899.     dom.bind(li, 'mouseover', function() {
  900.       dom.addClass(controller.__button, 'hover');
  901.     });
  902.  
  903.     dom.bind(li, 'mouseout', function() {
  904.       dom.removeClass(controller.__button, 'hover');
  905.     });
  906.   } else if (controller instanceof ColorController) {
  907.     dom.addClass(li, 'color');
  908.     controller.updateDisplay = common.compose(function(val) {
  909.       li.style.borderLeftColor = controller.__color.toString();
  910.       return val;
  911.     }, controller.updateDisplay);
  912.  
  913.     controller.updateDisplay();
  914.   }
  915.  
  916.   controller.setValue = common.compose(function(val) {
  917.     if (gui.getRoot().__preset_select && controller.isModified()) {
  918.       markPresetModified(gui.getRoot(), true);
  919.     }
  920.  
  921.     return val;
  922.   }, controller.setValue);
  923. }
  924.  
  925. function recallSavedValue(gui, controller) {
  926.   // Find the topmost GUI, that's where remembered objects live.
  927.   const root = gui.getRoot();
  928.  
  929.   // Does the object we're controlling match anything we've been told to
  930.   // remember?
  931.   const matchedIndex = root.__rememberedObjects.indexOf(controller.object);
  932.  
  933.   // Why yes, it does!
  934.   if (matchedIndex !== -1) {
  935.     // Let me fetch a map of controllers for thcommon.isObject.
  936.     let controllerMap = root.__rememberedObjectIndecesToControllers[matchedIndex];
  937.  
  938.     // Ohp, I believe this is the first controller we've created for this
  939.     // object. Lets make the map fresh.
  940.     if (controllerMap === undefined) {
  941.       controllerMap = {};
  942.       root.__rememberedObjectIndecesToControllers[matchedIndex] =
  943.         controllerMap;
  944.     }
  945.  
  946.     // Keep track of this controller
  947.     controllerMap[controller.property] = controller;
  948.  
  949.     // Okay, now have we saved any values for this controller?
  950.     if (root.load && root.load.remembered) {
  951.       const presetMap = root.load.remembered;
  952.  
  953.       // Which preset are we trying to load?
  954.       let preset;
  955.  
  956.       if (presetMap[gui.preset]) {
  957.         preset = presetMap[gui.preset];
  958.       } else if (presetMap[DEFAULT_DEFAULT_PRESET_NAME]) {
  959.         // Uhh, you can have the default instead?
  960.         preset = presetMap[DEFAULT_DEFAULT_PRESET_NAME];
  961.       } else {
  962.         // Nada.
  963.         return;
  964.       }
  965.  
  966.       // Did the loaded object remember thcommon.isObject? &&  Did we remember this particular property?
  967.       if (preset[matchedIndex] && preset[matchedIndex][controller.property] !== undefined) {
  968.         // We did remember something for this guy ...
  969.         const value = preset[matchedIndex][controller.property];
  970.  
  971.         // And that's what it is.
  972.         controller.initialValue = value;
  973.         controller.setValue(value);
  974.       }
  975.     }
  976.   }
  977. }
  978.  
  979. function add(gui, object, property, params) {
  980.   if (object[property] === undefined) {
  981.     throw new Error(`Object "${object}" has no property "${property}"`);
  982.   }
  983.  
  984.   let controller;
  985.  
  986.   if (params.color) {
  987.     controller = new ColorController(object, property);
  988.   } else {
  989.     const factoryArgs = [object, property].concat(params.factoryArgs);
  990.     controller = ControllerFactory.apply(gui, factoryArgs);
  991.   }
  992.  
  993.   if (params.before instanceof Controller) {
  994.     params.before = params.before.__li;
  995.   }
  996.  
  997.   recallSavedValue(gui, controller);
  998.  
  999.   dom.addClass(controller.domElement, 'c');
  1000.  
  1001.   const name = document.createElement('span');
  1002.   dom.addClass(name, 'property-name');
  1003.   name.innerHTML = controller.property;
  1004.  
  1005.   const container = document.createElement('div');
  1006.   container.appendChild(name);
  1007.   container.appendChild(controller.domElement);
  1008.  
  1009.   const li = addRow(gui, container, params.before);
  1010.  
  1011.   dom.addClass(li, GUI.CLASS_CONTROLLER_ROW);
  1012.   if (controller instanceof ColorController) {
  1013.     dom.addClass(li, 'color');
  1014.   } else {
  1015.     dom.addClass(li, typeof controller.getValue());
  1016.   }
  1017.  
  1018.   augmentController(gui, li, controller);
  1019.  
  1020.   gui.__controllers.push(controller);
  1021.  
  1022.   return controller;
  1023. }
  1024.  
  1025. function getLocalStorageHash(gui, key) {
  1026.   // TODO how does this deal with multiple GUI's?
  1027.   return document.location.href + '.' + key;
  1028. }
  1029.  
  1030. function addPresetOption(gui, name, setSelected) {
  1031.   const opt = document.createElement('option');
  1032.   opt.innerHTML = name;
  1033.   opt.value = name;
  1034.   gui.__preset_select.appendChild(opt);
  1035.   if (setSelected) {
  1036.     gui.__preset_select.selectedIndex = gui.__preset_select.length - 1;
  1037.   }
  1038. }
  1039.  
  1040. function showHideExplain(gui, explain) {
  1041.   explain.style.display = gui.useLocalStorage ? 'block' : 'none';
  1042. }
  1043.  
  1044. function addSaveMenu(gui) {
  1045.   const div = gui.__save_row = document.createElement('li');
  1046.  
  1047.   dom.addClass(gui.domElement, 'has-save');
  1048.  
  1049.   gui.__ul.insertBefore(div, gui.__ul.firstChild);
  1050.  
  1051.   dom.addClass(div, 'save-row');
  1052.  
  1053.   const gears = document.createElement('span');
  1054.   gears.innerHTML = '&nbsp;';
  1055.   dom.addClass(gears, 'button gears');
  1056.  
  1057.   // TODO replace with FunctionController
  1058.   const button = document.createElement('span');
  1059.   button.innerHTML = 'Save';
  1060.   dom.addClass(button, 'button');
  1061.   dom.addClass(button, 'save');
  1062.  
  1063.   const button2 = document.createElement('span');
  1064.   button2.innerHTML = 'New';
  1065.   dom.addClass(button2, 'button');
  1066.   dom.addClass(button2, 'save-as');
  1067.  
  1068.   const button3 = document.createElement('span');
  1069.   button3.innerHTML = 'Revert';
  1070.   dom.addClass(button3, 'button');
  1071.   dom.addClass(button3, 'revert');
  1072.  
  1073.   const select = gui.__preset_select = document.createElement('select');
  1074.  
  1075.   if (gui.load && gui.load.remembered) {
  1076.     common.each(gui.load.remembered, function(value, key) {
  1077.       addPresetOption(gui, key, key === gui.preset);
  1078.     });
  1079.   } else {
  1080.     addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false);
  1081.   }
  1082.  
  1083.   dom.bind(select, 'change', function() {
  1084.     for (let index = 0; index < gui.__preset_select.length; index++) {
  1085.       gui.__preset_select[index].innerHTML = gui.__preset_select[index].value;
  1086.     }
  1087.  
  1088.     gui.preset = this.value;
  1089.   });
  1090.  
  1091.   div.appendChild(select);
  1092.   div.appendChild(gears);
  1093.   div.appendChild(button);
  1094.   div.appendChild(button2);
  1095.   div.appendChild(button3);
  1096.  
  1097.   if (SUPPORTS_LOCAL_STORAGE) {
  1098.     const explain = document.getElementById('dg-local-explain');
  1099.     const localStorageCheckBox = document.getElementById('dg-local-storage');
  1100.     const saveLocally = document.getElementById('dg-save-locally');
  1101.  
  1102.     saveLocally.style.display = 'block';
  1103.  
  1104.     if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') {
  1105.       localStorageCheckBox.setAttribute('checked', 'checked');
  1106.     }
  1107.  
  1108.     showHideExplain(gui, explain);
  1109.  
  1110.     // TODO: Use a boolean controller, fool!
  1111.     dom.bind(localStorageCheckBox, 'change', function() {
  1112.       gui.useLocalStorage = !gui.useLocalStorage;
  1113.       showHideExplain(gui, explain);
  1114.     });
  1115.   }
  1116.  
  1117.   const newConstructorTextArea = document.getElementById('dg-new-constructor');
  1118.  
  1119.   dom.bind(newConstructorTextArea, 'keydown', function(e) {
  1120.     if (e.metaKey && (e.which === 67 || e.keyCode === 67)) {
  1121.       SAVE_DIALOGUE.hide();
  1122.     }
  1123.   });
  1124.  
  1125.   dom.bind(gears, 'click', function() {
  1126.     newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2);
  1127.     SAVE_DIALOGUE.show();
  1128.     newConstructorTextArea.focus();
  1129.     newConstructorTextArea.select();
  1130.   });
  1131.  
  1132.   dom.bind(button, 'click', function() {
  1133.     gui.save();
  1134.   });
  1135.  
  1136.   dom.bind(button2, 'click', function() {
  1137.     const presetName = prompt('Enter a new preset name.');
  1138.     if (presetName) {
  1139.       gui.saveAs(presetName);
  1140.     }
  1141.   });
  1142.  
  1143.   dom.bind(button3, 'click', function() {
  1144.     gui.revert();
  1145.   });
  1146.  
  1147.   // div.appendChild(button2);
  1148. }
  1149.  
  1150. function addResizeHandle(gui) {
  1151.   let pmouseX;
  1152.  
  1153.   gui.__resize_handle = document.createElement('div');
  1154.  
  1155.   common.extend(gui.__resize_handle.style, {
  1156.  
  1157.     width: '6px',
  1158.     marginLeft: '-3px',
  1159.     height: '200px',
  1160.     cursor: 'ew-resize',
  1161.     position: 'absolute'
  1162.     // border: '1px solid blue'
  1163.  
  1164.   });
  1165.  
  1166.   function drag(e) {
  1167.     e.preventDefault();
  1168.  
  1169.     gui.width += pmouseX - e.clientX;
  1170.     gui.onResize();
  1171.     pmouseX = e.clientX;
  1172.  
  1173.     return false;
  1174.   }
  1175.  
  1176.   function dragStop() {
  1177.     dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG);
  1178.     dom.unbind(window, 'mousemove', drag);
  1179.     dom.unbind(window, 'mouseup', dragStop);
  1180.   }
  1181.  
  1182.   function dragStart(e) {
  1183.     e.preventDefault();
  1184.  
  1185.     pmouseX = e.clientX;
  1186.  
  1187.     dom.addClass(gui.__closeButton, GUI.CLASS_DRAG);
  1188.     dom.bind(window, 'mousemove', drag);
  1189.     dom.bind(window, 'mouseup', dragStop);
  1190.  
  1191.     return false;
  1192.   }
  1193.  
  1194.   dom.bind(gui.__resize_handle, 'mousedown', dragStart);
  1195.   dom.bind(gui.__closeButton, 'mousedown', dragStart);
  1196.  
  1197.   gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild);
  1198. }
  1199.  
  1200. function setWidth(gui, w) {
  1201.   gui.domElement.style.width = w + 'px';
  1202.   // Auto placed save-rows are position fixed, so we have to
  1203.   // set the width manually if we want it to bleed to the edge
  1204.   if (gui.__save_row && gui.autoPlace) {
  1205.     gui.__save_row.style.width = w + 'px';
  1206.   }
  1207.   if (gui.__closeButton) {
  1208.     gui.__closeButton.style.width = w + 'px';
  1209.   }
  1210. }
  1211.  
  1212. function getCurrentPreset(gui, useInitialValues) {
  1213.   const toReturn = {};
  1214.  
  1215.   // For each object I'm remembering
  1216.   common.each(gui.__rememberedObjects, function(val, index) {
  1217.     const savedValues = {};
  1218.  
  1219.     // The controllers I've made for thcommon.isObject by property
  1220.     const controllerMap =
  1221.       gui.__rememberedObjectIndecesToControllers[index];
  1222.  
  1223.     // Remember each value for each property
  1224.     common.each(controllerMap, function(controller, property) {
  1225.       savedValues[property] = useInitialValues ? controller.initialValue : controller.getValue();
  1226.     });
  1227.  
  1228.     // Save the values for thcommon.isObject
  1229.     toReturn[index] = savedValues;
  1230.   });
  1231.  
  1232.   return toReturn;
  1233. }
  1234.  
  1235. function setPresetSelectIndex(gui) {
  1236.   for (let index = 0; index < gui.__preset_select.length; index++) {
  1237.     if (gui.__preset_select[index].value === gui.preset) {
  1238.       gui.__preset_select.selectedIndex = index;
  1239.     }
  1240.   }
  1241. }
  1242.  
  1243. function updateDisplays(controllerArray) {
  1244.   if (controllerArray.length !== 0) {
  1245.     requestAnimationFrame.call(window, function() {
  1246.       updateDisplays(controllerArray);
  1247.     });
  1248.   }
  1249.  
  1250.   common.each(controllerArray, function(c) {
  1251.     c.updateDisplay();
  1252.   });
  1253. }
  1254.  
  1255. module.exports = GUI;
  1256.  
downloadGUI.js Source code - Download dat.gui Source code
Related Source Codes/Software:
tmate - Instant Terminal Sharing ht... 2017-04-16
WNXHuntForCity - City find By Objective - C 2017-04-16
st2 - StackStorm (aka IFTTT for Ops) is event-driven aut... 2017-04-16
css-in-js - React: CSS in JS techniques comparison. 2017-04-16
android-viewflow - A horizontal view scroller library for Android 2017-04-16
capybara-webkit - A Capybara driver for headless WebKit to test Java... 2017-04-16
fullstackpython.com - Full Stack Python source with Pelican, Bootstrap a... 2017-04-16
naxsi - NAXSI is an open-source, high performance, low rul... 2017-04-16
ToGL - Direct3D to OpenGL abstraction layer 2017-04-16
LiveScript - LiveScript is a language which compiles to JavaScr... 2017-04-16
MaterialSearchView - Express it in the library to implement SearchView ... 2017-04-23
ptpython - A better Python REPL 2017-04-23
Laravel-4-Bootstrap-Starter-Site - Laravel 4 Starter Site is a basic blog application... 2017-04-23
official-images - Primary source of truth for the Docker "Official I... 2017-04-23
awesome-chrome-devtools - Awesome tooling and resources in the Chrome DevToo... 2017-04-23
EventStore - The open-source, functional database with Complex ... 2017-04-23
screencat - 2017-04-23
viz.js - A hack to put Graphviz on the web. ... 2017-04-23
JKeyboardPanelSwitch - For resolve the layout conflict when keybord & amp... 2017-04-23
mama2 - Mother plan - all firewood high flame 2017-04-23

 Back to top