├── demo1 └── index.html ├── demo3 ├── js │ ├── Stats.js │ └── tween.min.js ├── 3-2.html ├── 3-1.html ├── 3-5.html ├── 3-3.html └── 3-4.html ├── demo4-light ├── js │ ├── Stats.js │ ├── tween.min.js │ └── dat.gui.js ├── 5-1.html ├── 5-2.html ├── 5-3.html ├── 5-5.html ├── 5-4.html ├── 5-6.html └── 5-7.html └── demo2-line └── index.html /demo1/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | TRY 9 | 10 | 11 | 12 | 13 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /demo3/js/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r10 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=1E3,o=0,h=0,p=1E3,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; 3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div"); 4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display= 5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height= 6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}}; 7 | -------------------------------------------------------------------------------- /demo4-light/js/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r10 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=1E3,o=0,h=0,p=1E3,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; 3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div"); 4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display= 5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height= 6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}}; 7 | -------------------------------------------------------------------------------- /demo4-light/5-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 75 | 76 | 77 | 78 |
79 | 80 | -------------------------------------------------------------------------------- /demo4-light/5-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 79 | 80 | 81 | 82 |
83 | 84 | -------------------------------------------------------------------------------- /demo4-light/5-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 82 | 83 | 84 | 85 |
86 | 87 | -------------------------------------------------------------------------------- /demo3/3-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 88 | 89 | 90 | 91 |
92 | 93 | -------------------------------------------------------------------------------- /demo3/3-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 88 | 89 | 90 | 91 |
92 | 93 | -------------------------------------------------------------------------------- /demo2-line/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 16 | 98 | 99 | 100 | 101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /demo3/3-5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Three框架 7 | 8 | 9 | 10 | 11 | 20 | 115 | 116 | 117 | 118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /demo3/3-3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Three框架 7 | 8 | 9 | 10 | 19 | 115 | 116 | 117 | 118 |
119 | 120 | 121 | -------------------------------------------------------------------------------- /demo3/3-4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 8 | 9 | 10 | 20 | 108 | 109 | 110 | 111 |
112 |
113 | 114 | -------------------------------------------------------------------------------- /demo4-light/5-5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 106 | 107 | 108 | 109 |
110 | 111 | -------------------------------------------------------------------------------- /demo4-light/5-4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Three框架 6 | 7 | 17 | 107 | 108 | 109 | 110 |
111 | 112 | -------------------------------------------------------------------------------- /demo4-light/5-6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Three框架 7 | 8 | 17 | 120 | 121 | 122 | 123 |
124 | 125 | 126 | -------------------------------------------------------------------------------- /demo4-light/5-7.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Three框架 7 | 8 | 17 | 131 | 132 | 133 | 134 |
135 | 136 | 137 | -------------------------------------------------------------------------------- /demo3/js/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js - http://github.com/sole/tween.js 2 | 'use strict';var TWEEN=TWEEN||function(){var a=[];return{REVISION:"10",getAll:function(){return a},removeAll:function(){a=[]},add:function(c){a.push(c)},remove:function(c){c=a.indexOf(c);-1!==c&&a.splice(c,1)},update:function(c){if(0===a.length)return!1;for(var b=0,d=a.length,c=void 0!==c?c:void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();b(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a* 7 | a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1- 8 | Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)* 9 | 2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1- 10 | TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}}; 11 | TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.Linear;return 0>c?g(a[0],a[1],d):1b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,g=TWEEN.Interpolation.Utils.Bernstein,i;for(i=0;i<=d;i++)b+=e(1-c,d-i)*e(c,i)*a[i]*g(d,i);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),g(a[(e- 12 | 1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(g(a[0],a[0],a[1],a[1],-d)-a[0]):1(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a* 7 | a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1- 8 | Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)* 9 | 2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1- 10 | TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}}; 11 | TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.Linear;return 0>c?g(a[0],a[1],d):1b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,g=TWEEN.Interpolation.Utils.Bernstein,i;for(i=0;i<=d;i++)b+=e(1-c,d-i)*e(c,i)*a[i]*g(d,i);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),g(a[(e- 12 | 1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(g(a[0],a[0],a[1],a[1],-d)-a[0]):1= 0; i--) { 101 | args = [toCall[i].apply(this, args)]; 102 | } 103 | return args[0]; 104 | } 105 | }, 106 | 107 | each: function(obj, itr, scope) { 108 | 109 | 110 | if (ARR_EACH && obj.forEach === ARR_EACH) { 111 | 112 | obj.forEach(itr, scope); 113 | 114 | } else if (obj.length === obj.length + 0) { // Is number but not NaN 115 | 116 | for (var key = 0, l = obj.length; key < l; key++) 117 | if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) 118 | return; 119 | 120 | } else { 121 | 122 | for (var key in obj) 123 | if (itr.call(scope, obj[key], key) === this.BREAK) 124 | return; 125 | 126 | } 127 | 128 | }, 129 | 130 | defer: function(fnc) { 131 | setTimeout(fnc, 0); 132 | }, 133 | 134 | toArray: function(obj) { 135 | if (obj.toArray) return obj.toArray(); 136 | return ARR_SLICE.call(obj); 137 | }, 138 | 139 | isUndefined: function(obj) { 140 | return obj === undefined; 141 | }, 142 | 143 | isNull: function(obj) { 144 | return obj === null; 145 | }, 146 | 147 | isNaN: function(obj) { 148 | return obj !== obj; 149 | }, 150 | 151 | isArray: Array.isArray || function(obj) { 152 | return obj.constructor === Array; 153 | }, 154 | 155 | isObject: function(obj) { 156 | return obj === Object(obj); 157 | }, 158 | 159 | isNumber: function(obj) { 160 | return obj === obj+0; 161 | }, 162 | 163 | isString: function(obj) { 164 | return obj === obj+''; 165 | }, 166 | 167 | isBoolean: function(obj) { 168 | return obj === false || obj === true; 169 | }, 170 | 171 | isFunction: function(obj) { 172 | return Object.prototype.toString.call(obj) === '[object Function]'; 173 | } 174 | 175 | }; 176 | 177 | })(); 178 | 179 | 180 | dat.controllers.Controller = (function (common) { 181 | 182 | /** 183 | * @class An "abstract" class that represents a given property of an object. 184 | * 185 | * @param {Object} object The object to be manipulated 186 | * @param {string} property The name of the property to be manipulated 187 | * 188 | * @member dat.controllers 189 | */ 190 | var Controller = function(object, property) { 191 | 192 | this.initialValue = object[property]; 193 | 194 | /** 195 | * Those who extend this class will put their DOM elements in here. 196 | * @type {DOMElement} 197 | */ 198 | this.domElement = document.createElement('div'); 199 | 200 | /** 201 | * The object to manipulate 202 | * @type {Object} 203 | */ 204 | this.object = object; 205 | 206 | /** 207 | * The name of the property to manipulate 208 | * @type {String} 209 | */ 210 | this.property = property; 211 | 212 | /** 213 | * The function to be called on change. 214 | * @type {Function} 215 | * @ignore 216 | */ 217 | this.__onChange = undefined; 218 | 219 | /** 220 | * The function to be called on finishing change. 221 | * @type {Function} 222 | * @ignore 223 | */ 224 | this.__onFinishChange = undefined; 225 | 226 | }; 227 | 228 | common.extend( 229 | 230 | Controller.prototype, 231 | 232 | /** @lends dat.controllers.Controller.prototype */ 233 | { 234 | 235 | /** 236 | * Specify that a function fire every time someone changes the value with 237 | * this Controller. 238 | * 239 | * @param {Function} fnc This function will be called whenever the value 240 | * is modified via this Controller. 241 | * @returns {dat.controllers.Controller} this 242 | */ 243 | onChange: function(fnc) { 244 | this.__onChange = fnc; 245 | return this; 246 | }, 247 | 248 | /** 249 | * Specify that a function fire every time someone "finishes" changing 250 | * the value wih this Controller. Useful for values that change 251 | * incrementally like numbers or strings. 252 | * 253 | * @param {Function} fnc This function will be called whenever 254 | * someone "finishes" changing the value via this Controller. 255 | * @returns {dat.controllers.Controller} this 256 | */ 257 | onFinishChange: function(fnc) { 258 | this.__onFinishChange = fnc; 259 | return this; 260 | }, 261 | 262 | /** 263 | * Change the value of object[property] 264 | * 265 | * @param {Object} newValue The new value of object[property] 266 | */ 267 | setValue: function(newValue) { 268 | this.object[this.property] = newValue; 269 | if (this.__onChange) { 270 | this.__onChange.call(this, newValue); 271 | } 272 | this.updateDisplay(); 273 | return this; 274 | }, 275 | 276 | /** 277 | * Gets the value of object[property] 278 | * 279 | * @returns {Object} The current value of object[property] 280 | */ 281 | getValue: function() { 282 | return this.object[this.property]; 283 | }, 284 | 285 | /** 286 | * Refreshes the visual display of a Controller in order to keep sync 287 | * with the object's current value. 288 | * @returns {dat.controllers.Controller} this 289 | */ 290 | updateDisplay: function() { 291 | return this; 292 | }, 293 | 294 | /** 295 | * @returns {Boolean} true if the value has deviated from initialValue 296 | */ 297 | isModified: function() { 298 | return this.initialValue !== this.getValue() 299 | } 300 | 301 | } 302 | 303 | ); 304 | 305 | return Controller; 306 | 307 | 308 | })(dat.utils.common); 309 | 310 | 311 | dat.dom.dom = (function (common) { 312 | 313 | var EVENT_MAP = { 314 | 'HTMLEvents': ['change'], 315 | 'MouseEvents': ['click','mousemove','mousedown','mouseup', 'mouseover'], 316 | 'KeyboardEvents': ['keydown'] 317 | }; 318 | 319 | var EVENT_MAP_INV = {}; 320 | common.each(EVENT_MAP, function(v, k) { 321 | common.each(v, function(e) { 322 | EVENT_MAP_INV[e] = k; 323 | }); 324 | }); 325 | 326 | var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/; 327 | 328 | function cssValueToPixels(val) { 329 | 330 | if (val === '0' || common.isUndefined(val)) return 0; 331 | 332 | var match = val.match(CSS_VALUE_PIXELS); 333 | 334 | if (!common.isNull(match)) { 335 | return parseFloat(match[1]); 336 | } 337 | 338 | // TODO ...ems? %? 339 | 340 | return 0; 341 | 342 | } 343 | 344 | /** 345 | * @namespace 346 | * @member dat.dom 347 | */ 348 | var dom = { 349 | 350 | /** 351 | * 352 | * @param elem 353 | * @param selectable 354 | */ 355 | makeSelectable: function(elem, selectable) { 356 | 357 | if (elem === undefined || elem.style === undefined) return; 358 | 359 | elem.onselectstart = selectable ? function() { 360 | return false; 361 | } : function() { 362 | }; 363 | 364 | elem.style.MozUserSelect = selectable ? 'auto' : 'none'; 365 | elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none'; 366 | elem.unselectable = selectable ? 'on' : 'off'; 367 | 368 | }, 369 | 370 | /** 371 | * 372 | * @param elem 373 | * @param horizontal 374 | * @param vertical 375 | */ 376 | makeFullscreen: function(elem, horizontal, vertical) { 377 | 378 | if (common.isUndefined(horizontal)) horizontal = true; 379 | if (common.isUndefined(vertical)) vertical = true; 380 | 381 | elem.style.position = 'absolute'; 382 | 383 | if (horizontal) { 384 | elem.style.left = 0; 385 | elem.style.right = 0; 386 | } 387 | if (vertical) { 388 | elem.style.top = 0; 389 | elem.style.bottom = 0; 390 | } 391 | 392 | }, 393 | 394 | /** 395 | * 396 | * @param elem 397 | * @param eventType 398 | * @param params 399 | */ 400 | fakeEvent: function(elem, eventType, params, aux) { 401 | params = params || {}; 402 | var className = EVENT_MAP_INV[eventType]; 403 | if (!className) { 404 | throw new Error('Event type ' + eventType + ' not supported.'); 405 | } 406 | var evt = document.createEvent(className); 407 | switch (className) { 408 | case 'MouseEvents': 409 | var clientX = params.x || params.clientX || 0; 410 | var clientY = params.y || params.clientY || 0; 411 | evt.initMouseEvent(eventType, params.bubbles || false, 412 | params.cancelable || true, window, params.clickCount || 1, 413 | 0, //screen X 414 | 0, //screen Y 415 | clientX, //client X 416 | clientY, //client Y 417 | false, false, false, false, 0, null); 418 | break; 419 | case 'KeyboardEvents': 420 | var init = evt.initKeyboardEvent || evt.initKeyEvent; // webkit || moz 421 | common.defaults(params, { 422 | cancelable: true, 423 | ctrlKey: false, 424 | altKey: false, 425 | shiftKey: false, 426 | metaKey: false, 427 | keyCode: undefined, 428 | charCode: undefined 429 | }); 430 | init(eventType, params.bubbles || false, 431 | params.cancelable, window, 432 | params.ctrlKey, params.altKey, 433 | params.shiftKey, params.metaKey, 434 | params.keyCode, params.charCode); 435 | break; 436 | default: 437 | evt.initEvent(eventType, params.bubbles || false, 438 | params.cancelable || true); 439 | break; 440 | } 441 | common.defaults(evt, aux); 442 | elem.dispatchEvent(evt); 443 | }, 444 | 445 | /** 446 | * 447 | * @param elem 448 | * @param event 449 | * @param func 450 | * @param bool 451 | */ 452 | bind: function(elem, event, func, bool) { 453 | bool = bool || false; 454 | if (elem.addEventListener) 455 | elem.addEventListener(event, func, bool); 456 | else if (elem.attachEvent) 457 | elem.attachEvent('on' + event, func); 458 | return dom; 459 | }, 460 | 461 | /** 462 | * 463 | * @param elem 464 | * @param event 465 | * @param func 466 | * @param bool 467 | */ 468 | unbind: function(elem, event, func, bool) { 469 | bool = bool || false; 470 | if (elem.removeEventListener) 471 | elem.removeEventListener(event, func, bool); 472 | else if (elem.detachEvent) 473 | elem.detachEvent('on' + event, func); 474 | return dom; 475 | }, 476 | 477 | /** 478 | * 479 | * @param elem 480 | * @param className 481 | */ 482 | addClass: function(elem, className) { 483 | if (elem.className === undefined) { 484 | elem.className = className; 485 | } else if (elem.className !== className) { 486 | var classes = elem.className.split(/ +/); 487 | if (classes.indexOf(className) == -1) { 488 | classes.push(className); 489 | elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, ''); 490 | } 491 | } 492 | return dom; 493 | }, 494 | 495 | /** 496 | * 497 | * @param elem 498 | * @param className 499 | */ 500 | removeClass: function(elem, className) { 501 | if (className) { 502 | if (elem.className === undefined) { 503 | // elem.className = className; 504 | } else if (elem.className === className) { 505 | elem.removeAttribute('class'); 506 | } else { 507 | var classes = elem.className.split(/ +/); 508 | var index = classes.indexOf(className); 509 | if (index != -1) { 510 | classes.splice(index, 1); 511 | elem.className = classes.join(' '); 512 | } 513 | } 514 | } else { 515 | elem.className = undefined; 516 | } 517 | return dom; 518 | }, 519 | 520 | hasClass: function(elem, className) { 521 | return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false; 522 | }, 523 | 524 | /** 525 | * 526 | * @param elem 527 | */ 528 | getWidth: function(elem) { 529 | 530 | var style = getComputedStyle(elem); 531 | 532 | return cssValueToPixels(style['border-left-width']) + 533 | cssValueToPixels(style['border-right-width']) + 534 | cssValueToPixels(style['padding-left']) + 535 | cssValueToPixels(style['padding-right']) + 536 | cssValueToPixels(style['width']); 537 | }, 538 | 539 | /** 540 | * 541 | * @param elem 542 | */ 543 | getHeight: function(elem) { 544 | 545 | var style = getComputedStyle(elem); 546 | 547 | return cssValueToPixels(style['border-top-width']) + 548 | cssValueToPixels(style['border-bottom-width']) + 549 | cssValueToPixels(style['padding-top']) + 550 | cssValueToPixels(style['padding-bottom']) + 551 | cssValueToPixels(style['height']); 552 | }, 553 | 554 | /** 555 | * 556 | * @param elem 557 | */ 558 | getOffset: function(elem) { 559 | var offset = {left: 0, top:0}; 560 | if (elem.offsetParent) { 561 | do { 562 | offset.left += elem.offsetLeft; 563 | offset.top += elem.offsetTop; 564 | } while (elem = elem.offsetParent); 565 | } 566 | return offset; 567 | }, 568 | 569 | // http://stackoverflow.com/posts/2684561/revisions 570 | /** 571 | * 572 | * @param elem 573 | */ 574 | isActive: function(elem) { 575 | return elem === document.activeElement && ( elem.type || elem.href ); 576 | } 577 | 578 | }; 579 | 580 | return dom; 581 | 582 | })(dat.utils.common); 583 | 584 | 585 | dat.controllers.OptionController = (function (Controller, dom, common) { 586 | 587 | /** 588 | * @class Provides a select input to alter the property of an object, using a 589 | * list of accepted values. 590 | * 591 | * @extends dat.controllers.Controller 592 | * 593 | * @param {Object} object The object to be manipulated 594 | * @param {string} property The name of the property to be manipulated 595 | * @param {Object|string[]} options A map of labels to acceptable values, or 596 | * a list of acceptable string values. 597 | * 598 | * @member dat.controllers 599 | */ 600 | var OptionController = function(object, property, options) { 601 | 602 | OptionController.superclass.call(this, object, property); 603 | 604 | var _this = this; 605 | 606 | /** 607 | * The drop down menu 608 | * @ignore 609 | */ 610 | this.__select = document.createElement('select'); 611 | 612 | if (common.isArray(options)) { 613 | var map = {}; 614 | common.each(options, function(element) { 615 | map[element] = element; 616 | }); 617 | options = map; 618 | } 619 | 620 | common.each(options, function(value, key) { 621 | 622 | var opt = document.createElement('option'); 623 | opt.innerHTML = key; 624 | opt.setAttribute('value', value); 625 | _this.__select.appendChild(opt); 626 | 627 | }); 628 | 629 | // Acknowledge original value 630 | this.updateDisplay(); 631 | 632 | dom.bind(this.__select, 'change', function() { 633 | var desiredValue = this.options[this.selectedIndex].value; 634 | _this.setValue(desiredValue); 635 | }); 636 | 637 | this.domElement.appendChild(this.__select); 638 | 639 | }; 640 | 641 | OptionController.superclass = Controller; 642 | 643 | common.extend( 644 | 645 | OptionController.prototype, 646 | Controller.prototype, 647 | 648 | { 649 | 650 | setValue: function(v) { 651 | var toReturn = OptionController.superclass.prototype.setValue.call(this, v); 652 | if (this.__onFinishChange) { 653 | this.__onFinishChange.call(this, this.getValue()); 654 | } 655 | return toReturn; 656 | }, 657 | 658 | updateDisplay: function() { 659 | this.__select.value = this.getValue(); 660 | return OptionController.superclass.prototype.updateDisplay.call(this); 661 | } 662 | 663 | } 664 | 665 | ); 666 | 667 | return OptionController; 668 | 669 | })(dat.controllers.Controller, 670 | dat.dom.dom, 671 | dat.utils.common); 672 | 673 | 674 | dat.controllers.NumberController = (function (Controller, common) { 675 | 676 | /** 677 | * @class Represents a given property of an object that is a number. 678 | * 679 | * @extends dat.controllers.Controller 680 | * 681 | * @param {Object} object The object to be manipulated 682 | * @param {string} property The name of the property to be manipulated 683 | * @param {Object} [params] Optional parameters 684 | * @param {Number} [params.min] Minimum allowed value 685 | * @param {Number} [params.max] Maximum allowed value 686 | * @param {Number} [params.step] Increment by which to change value 687 | * 688 | * @member dat.controllers 689 | */ 690 | var NumberController = function(object, property, params) { 691 | 692 | NumberController.superclass.call(this, object, property); 693 | 694 | params = params || {}; 695 | 696 | this.__min = params.min; 697 | this.__max = params.max; 698 | this.__step = params.step; 699 | 700 | if (common.isUndefined(this.__step)) { 701 | 702 | if (this.initialValue == 0) { 703 | this.__impliedStep = 1; // What are we, psychics? 704 | } else { 705 | // Hey Doug, check this out. 706 | this.__impliedStep = Math.pow(10, Math.floor(Math.log(this.initialValue)/Math.LN10))/10; 707 | } 708 | 709 | } else { 710 | 711 | this.__impliedStep = this.__step; 712 | 713 | } 714 | 715 | this.__precision = numDecimals(this.__impliedStep); 716 | 717 | 718 | }; 719 | 720 | NumberController.superclass = Controller; 721 | 722 | common.extend( 723 | 724 | NumberController.prototype, 725 | Controller.prototype, 726 | 727 | /** @lends dat.controllers.NumberController.prototype */ 728 | { 729 | 730 | setValue: function(v) { 731 | 732 | if (this.__min !== undefined && v < this.__min) { 733 | v = this.__min; 734 | } else if (this.__max !== undefined && v > this.__max) { 735 | v = this.__max; 736 | } 737 | 738 | if (this.__step !== undefined && v % this.__step != 0) { 739 | v = Math.round(v / this.__step) * this.__step; 740 | } 741 | 742 | return NumberController.superclass.prototype.setValue.call(this, v); 743 | 744 | }, 745 | 746 | /** 747 | * Specify a minimum value for object[property]. 748 | * 749 | * @param {Number} minValue The minimum value for 750 | * object[property] 751 | * @returns {dat.controllers.NumberController} this 752 | */ 753 | min: function(v) { 754 | this.__min = v; 755 | return this; 756 | }, 757 | 758 | /** 759 | * Specify a maximum value for object[property]. 760 | * 761 | * @param {Number} maxValue The maximum value for 762 | * object[property] 763 | * @returns {dat.controllers.NumberController} this 764 | */ 765 | max: function(v) { 766 | this.__max = v; 767 | return this; 768 | }, 769 | 770 | /** 771 | * Specify a step value that dat.controllers.NumberController 772 | * increments by. 773 | * 774 | * @param {Number} stepValue The step value for 775 | * dat.controllers.NumberController 776 | * @default if minimum and maximum specified increment is 1% of the 777 | * difference otherwise stepValue is 1 778 | * @returns {dat.controllers.NumberController} this 779 | */ 780 | step: function(v) { 781 | this.__step = v; 782 | return this; 783 | } 784 | 785 | } 786 | 787 | ); 788 | 789 | function numDecimals(x) { 790 | x = x.toString(); 791 | if (x.indexOf('.') > -1) { 792 | return x.length - x.indexOf('.') - 1; 793 | } else { 794 | return 0; 795 | } 796 | } 797 | 798 | return NumberController; 799 | 800 | })(dat.controllers.Controller, 801 | dat.utils.common); 802 | 803 | 804 | dat.controllers.NumberControllerBox = (function (NumberController, dom, common) { 805 | 806 | /** 807 | * @class Represents a given property of an object that is a number and 808 | * provides an input element with which to manipulate it. 809 | * 810 | * @extends dat.controllers.Controller 811 | * @extends dat.controllers.NumberController 812 | * 813 | * @param {Object} object The object to be manipulated 814 | * @param {string} property The name of the property to be manipulated 815 | * @param {Object} [params] Optional parameters 816 | * @param {Number} [params.min] Minimum allowed value 817 | * @param {Number} [params.max] Maximum allowed value 818 | * @param {Number} [params.step] Increment by which to change value 819 | * 820 | * @member dat.controllers 821 | */ 822 | var NumberControllerBox = function(object, property, params) { 823 | 824 | this.__truncationSuspended = false; 825 | 826 | NumberControllerBox.superclass.call(this, object, property, params); 827 | 828 | var _this = this; 829 | 830 | /** 831 | * {Number} Previous mouse y position 832 | * @ignore 833 | */ 834 | var prev_y; 835 | 836 | this.__input = document.createElement('input'); 837 | this.__input.setAttribute('type', 'text'); 838 | 839 | // Makes it so manually specified values are not truncated. 840 | 841 | dom.bind(this.__input, 'change', onChange); 842 | dom.bind(this.__input, 'blur', onBlur); 843 | dom.bind(this.__input, 'mousedown', onMouseDown); 844 | dom.bind(this.__input, 'keydown', function(e) { 845 | 846 | // When pressing entire, you can be as precise as you want. 847 | if (e.keyCode === 13) { 848 | _this.__truncationSuspended = true; 849 | this.blur(); 850 | _this.__truncationSuspended = false; 851 | } 852 | 853 | }); 854 | 855 | function onChange() { 856 | var attempted = parseFloat(_this.__input.value); 857 | if (!common.isNaN(attempted)) _this.setValue(attempted); 858 | } 859 | 860 | function onBlur() { 861 | onChange(); 862 | if (_this.__onFinishChange) { 863 | _this.__onFinishChange.call(_this, _this.getValue()); 864 | } 865 | } 866 | 867 | function onMouseDown(e) { 868 | dom.bind(window, 'mousemove', onMouseDrag); 869 | dom.bind(window, 'mouseup', onMouseUp); 870 | prev_y = e.clientY; 871 | } 872 | 873 | function onMouseDrag(e) { 874 | 875 | var diff = prev_y - e.clientY; 876 | _this.setValue(_this.getValue() + diff * _this.__impliedStep); 877 | 878 | prev_y = e.clientY; 879 | 880 | } 881 | 882 | function onMouseUp() { 883 | dom.unbind(window, 'mousemove', onMouseDrag); 884 | dom.unbind(window, 'mouseup', onMouseUp); 885 | } 886 | 887 | this.updateDisplay(); 888 | 889 | this.domElement.appendChild(this.__input); 890 | 891 | }; 892 | 893 | NumberControllerBox.superclass = NumberController; 894 | 895 | common.extend( 896 | 897 | NumberControllerBox.prototype, 898 | NumberController.prototype, 899 | 900 | { 901 | 902 | updateDisplay: function() { 903 | 904 | this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision); 905 | return NumberControllerBox.superclass.prototype.updateDisplay.call(this); 906 | } 907 | 908 | } 909 | 910 | ); 911 | 912 | function roundToDecimal(value, decimals) { 913 | var tenTo = Math.pow(10, decimals); 914 | return Math.round(value * tenTo) / tenTo; 915 | } 916 | 917 | return NumberControllerBox; 918 | 919 | })(dat.controllers.NumberController, 920 | dat.dom.dom, 921 | dat.utils.common); 922 | 923 | 924 | dat.controllers.NumberControllerSlider = (function (NumberController, dom, css, common, styleSheet) { 925 | 926 | /** 927 | * @class Represents a given property of an object that is a number, contains 928 | * a minimum and maximum, and provides a slider element with which to 929 | * manipulate it. It should be noted that the slider element is made up of 930 | * <div> tags, not the html5 931 | * <slider> element. 932 | * 933 | * @extends dat.controllers.Controller 934 | * @extends dat.controllers.NumberController 935 | * 936 | * @param {Object} object The object to be manipulated 937 | * @param {string} property The name of the property to be manipulated 938 | * @param {Number} minValue Minimum allowed value 939 | * @param {Number} maxValue Maximum allowed value 940 | * @param {Number} stepValue Increment by which to change value 941 | * 942 | * @member dat.controllers 943 | */ 944 | var NumberControllerSlider = function(object, property, min, max, step) { 945 | 946 | NumberControllerSlider.superclass.call(this, object, property, { min: min, max: max, step: step }); 947 | 948 | var _this = this; 949 | 950 | this.__background = document.createElement('div'); 951 | this.__foreground = document.createElement('div'); 952 | 953 | 954 | 955 | dom.bind(this.__background, 'mousedown', onMouseDown); 956 | 957 | dom.addClass(this.__background, 'slider'); 958 | dom.addClass(this.__foreground, 'slider-fg'); 959 | 960 | function onMouseDown(e) { 961 | 962 | dom.bind(window, 'mousemove', onMouseDrag); 963 | dom.bind(window, 'mouseup', onMouseUp); 964 | 965 | onMouseDrag(e); 966 | } 967 | 968 | function onMouseDrag(e) { 969 | 970 | e.preventDefault(); 971 | 972 | var offset = dom.getOffset(_this.__background); 973 | var width = dom.getWidth(_this.__background); 974 | 975 | _this.setValue( 976 | map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max) 977 | ); 978 | 979 | return false; 980 | 981 | } 982 | 983 | function onMouseUp() { 984 | dom.unbind(window, 'mousemove', onMouseDrag); 985 | dom.unbind(window, 'mouseup', onMouseUp); 986 | if (_this.__onFinishChange) { 987 | _this.__onFinishChange.call(_this, _this.getValue()); 988 | } 989 | } 990 | 991 | this.updateDisplay(); 992 | 993 | this.__background.appendChild(this.__foreground); 994 | this.domElement.appendChild(this.__background); 995 | 996 | }; 997 | 998 | NumberControllerSlider.superclass = NumberController; 999 | 1000 | /** 1001 | * Injects default stylesheet for slider elements. 1002 | */ 1003 | NumberControllerSlider.useDefaultStyles = function() { 1004 | css.inject(styleSheet); 1005 | }; 1006 | 1007 | common.extend( 1008 | 1009 | NumberControllerSlider.prototype, 1010 | NumberController.prototype, 1011 | 1012 | { 1013 | 1014 | updateDisplay: function() { 1015 | var pct = (this.getValue() - this.__min)/(this.__max - this.__min); 1016 | this.__foreground.style.width = pct*100+'%'; 1017 | return NumberControllerSlider.superclass.prototype.updateDisplay.call(this); 1018 | } 1019 | 1020 | } 1021 | 1022 | 1023 | 1024 | ); 1025 | 1026 | function map(v, i1, i2, o1, o2) { 1027 | return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); 1028 | } 1029 | 1030 | return NumberControllerSlider; 1031 | 1032 | })(dat.controllers.NumberController, 1033 | dat.dom.dom, 1034 | dat.utils.css, 1035 | dat.utils.common, 1036 | ".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 1037 | 1038 | 1039 | dat.controllers.FunctionController = (function (Controller, dom, common) { 1040 | 1041 | /** 1042 | * @class Provides a GUI interface to fire a specified method, a property of an object. 1043 | * 1044 | * @extends dat.controllers.Controller 1045 | * 1046 | * @param {Object} object The object to be manipulated 1047 | * @param {string} property The name of the property to be manipulated 1048 | * 1049 | * @member dat.controllers 1050 | */ 1051 | var FunctionController = function(object, property, text) { 1052 | 1053 | FunctionController.superclass.call(this, object, property); 1054 | 1055 | var _this = this; 1056 | 1057 | this.__button = document.createElement('div'); 1058 | this.__button.innerHTML = text === undefined ? 'Fire' : text; 1059 | dom.bind(this.__button, 'click', function(e) { 1060 | e.preventDefault(); 1061 | _this.fire(); 1062 | return false; 1063 | }); 1064 | 1065 | dom.addClass(this.__button, 'button'); 1066 | 1067 | this.domElement.appendChild(this.__button); 1068 | 1069 | 1070 | }; 1071 | 1072 | FunctionController.superclass = Controller; 1073 | 1074 | common.extend( 1075 | 1076 | FunctionController.prototype, 1077 | Controller.prototype, 1078 | { 1079 | 1080 | fire: function() { 1081 | if (this.__onChange) { 1082 | this.__onChange.call(this); 1083 | } 1084 | if (this.__onFinishChange) { 1085 | this.__onFinishChange.call(this, this.getValue()); 1086 | } 1087 | this.getValue().call(this.object); 1088 | } 1089 | } 1090 | 1091 | ); 1092 | 1093 | return FunctionController; 1094 | 1095 | })(dat.controllers.Controller, 1096 | dat.dom.dom, 1097 | dat.utils.common); 1098 | 1099 | 1100 | dat.controllers.BooleanController = (function (Controller, dom, common) { 1101 | 1102 | /** 1103 | * @class Provides a checkbox input to alter the boolean property of an object. 1104 | * @extends dat.controllers.Controller 1105 | * 1106 | * @param {Object} object The object to be manipulated 1107 | * @param {string} property The name of the property to be manipulated 1108 | * 1109 | * @member dat.controllers 1110 | */ 1111 | var BooleanController = function(object, property) { 1112 | 1113 | BooleanController.superclass.call(this, object, property); 1114 | 1115 | var _this = this; 1116 | this.__prev = this.getValue(); 1117 | 1118 | this.__checkbox = document.createElement('input'); 1119 | this.__checkbox.setAttribute('type', 'checkbox'); 1120 | 1121 | 1122 | dom.bind(this.__checkbox, 'change', onChange, false); 1123 | 1124 | this.domElement.appendChild(this.__checkbox); 1125 | 1126 | // Match original value 1127 | this.updateDisplay(); 1128 | 1129 | function onChange() { 1130 | _this.setValue(!_this.__prev); 1131 | } 1132 | 1133 | }; 1134 | 1135 | BooleanController.superclass = Controller; 1136 | 1137 | common.extend( 1138 | 1139 | BooleanController.prototype, 1140 | Controller.prototype, 1141 | 1142 | { 1143 | 1144 | setValue: function(v) { 1145 | var toReturn = BooleanController.superclass.prototype.setValue.call(this, v); 1146 | if (this.__onFinishChange) { 1147 | this.__onFinishChange.call(this, this.getValue()); 1148 | } 1149 | this.__prev = this.getValue(); 1150 | return toReturn; 1151 | }, 1152 | 1153 | updateDisplay: function() { 1154 | 1155 | if (this.getValue() === true) { 1156 | this.__checkbox.setAttribute('checked', 'checked'); 1157 | this.__checkbox.checked = true; 1158 | } else { 1159 | this.__checkbox.checked = false; 1160 | } 1161 | 1162 | return BooleanController.superclass.prototype.updateDisplay.call(this); 1163 | 1164 | } 1165 | 1166 | 1167 | } 1168 | 1169 | ); 1170 | 1171 | return BooleanController; 1172 | 1173 | })(dat.controllers.Controller, 1174 | dat.dom.dom, 1175 | dat.utils.common); 1176 | 1177 | 1178 | dat.color.toString = (function (common) { 1179 | 1180 | return function(color) { 1181 | 1182 | if (color.a == 1 || common.isUndefined(color.a)) { 1183 | 1184 | var s = color.hex.toString(16); 1185 | while (s.length < 6) { 1186 | s = '0' + s; 1187 | } 1188 | 1189 | return '#' + s; 1190 | 1191 | } else { 1192 | 1193 | return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')'; 1194 | 1195 | } 1196 | 1197 | } 1198 | 1199 | })(dat.utils.common); 1200 | 1201 | 1202 | dat.color.interpret = (function (toString, common) { 1203 | 1204 | var result, toReturn; 1205 | 1206 | var interpret = function() { 1207 | 1208 | toReturn = false; 1209 | 1210 | var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0]; 1211 | 1212 | common.each(INTERPRETATIONS, function(family) { 1213 | 1214 | if (family.litmus(original)) { 1215 | 1216 | common.each(family.conversions, function(conversion, conversionName) { 1217 | 1218 | result = conversion.read(original); 1219 | 1220 | if (toReturn === false && result !== false) { 1221 | toReturn = result; 1222 | result.conversionName = conversionName; 1223 | result.conversion = conversion; 1224 | return common.BREAK; 1225 | 1226 | } 1227 | 1228 | }); 1229 | 1230 | return common.BREAK; 1231 | 1232 | } 1233 | 1234 | }); 1235 | 1236 | return toReturn; 1237 | 1238 | }; 1239 | 1240 | var INTERPRETATIONS = [ 1241 | 1242 | // Strings 1243 | { 1244 | 1245 | litmus: common.isString, 1246 | 1247 | conversions: { 1248 | 1249 | THREE_CHAR_HEX: { 1250 | 1251 | read: function(original) { 1252 | 1253 | var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); 1254 | if (test === null) return false; 1255 | 1256 | return { 1257 | space: 'HEX', 1258 | hex: parseInt( 1259 | '0x' + 1260 | test[1].toString() + test[1].toString() + 1261 | test[2].toString() + test[2].toString() + 1262 | test[3].toString() + test[3].toString()) 1263 | }; 1264 | 1265 | }, 1266 | 1267 | write: toString 1268 | 1269 | }, 1270 | 1271 | SIX_CHAR_HEX: { 1272 | 1273 | read: function(original) { 1274 | 1275 | var test = original.match(/^#([A-F0-9]{6})$/i); 1276 | if (test === null) return false; 1277 | 1278 | return { 1279 | space: 'HEX', 1280 | hex: parseInt('0x' + test[1].toString()) 1281 | }; 1282 | 1283 | }, 1284 | 1285 | write: toString 1286 | 1287 | }, 1288 | 1289 | CSS_RGB: { 1290 | 1291 | read: function(original) { 1292 | 1293 | var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 1294 | if (test === null) return false; 1295 | 1296 | return { 1297 | space: 'RGB', 1298 | r: parseFloat(test[1]), 1299 | g: parseFloat(test[2]), 1300 | b: parseFloat(test[3]) 1301 | }; 1302 | 1303 | }, 1304 | 1305 | write: toString 1306 | 1307 | }, 1308 | 1309 | CSS_RGBA: { 1310 | 1311 | read: function(original) { 1312 | 1313 | var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/); 1314 | if (test === null) return false; 1315 | 1316 | return { 1317 | space: 'RGB', 1318 | r: parseFloat(test[1]), 1319 | g: parseFloat(test[2]), 1320 | b: parseFloat(test[3]), 1321 | a: parseFloat(test[4]) 1322 | }; 1323 | 1324 | }, 1325 | 1326 | write: toString 1327 | 1328 | } 1329 | 1330 | } 1331 | 1332 | }, 1333 | 1334 | // Numbers 1335 | { 1336 | 1337 | litmus: common.isNumber, 1338 | 1339 | conversions: { 1340 | 1341 | HEX: { 1342 | read: function(original) { 1343 | return { 1344 | space: 'HEX', 1345 | hex: original, 1346 | conversionName: 'HEX' 1347 | } 1348 | }, 1349 | 1350 | write: function(color) { 1351 | return color.hex; 1352 | } 1353 | } 1354 | 1355 | } 1356 | 1357 | }, 1358 | 1359 | // Arrays 1360 | { 1361 | 1362 | litmus: common.isArray, 1363 | 1364 | conversions: { 1365 | 1366 | RGB_ARRAY: { 1367 | read: function(original) { 1368 | if (original.length != 3) return false; 1369 | return { 1370 | space: 'RGB', 1371 | r: original[0], 1372 | g: original[1], 1373 | b: original[2] 1374 | }; 1375 | }, 1376 | 1377 | write: function(color) { 1378 | return [color.r, color.g, color.b]; 1379 | } 1380 | 1381 | }, 1382 | 1383 | RGBA_ARRAY: { 1384 | read: function(original) { 1385 | if (original.length != 4) return false; 1386 | return { 1387 | space: 'RGB', 1388 | r: original[0], 1389 | g: original[1], 1390 | b: original[2], 1391 | a: original[3] 1392 | }; 1393 | }, 1394 | 1395 | write: function(color) { 1396 | return [color.r, color.g, color.b, color.a]; 1397 | } 1398 | 1399 | } 1400 | 1401 | } 1402 | 1403 | }, 1404 | 1405 | // Objects 1406 | { 1407 | 1408 | litmus: common.isObject, 1409 | 1410 | conversions: { 1411 | 1412 | RGBA_OBJ: { 1413 | read: function(original) { 1414 | if (common.isNumber(original.r) && 1415 | common.isNumber(original.g) && 1416 | common.isNumber(original.b) && 1417 | common.isNumber(original.a)) { 1418 | return { 1419 | space: 'RGB', 1420 | r: original.r, 1421 | g: original.g, 1422 | b: original.b, 1423 | a: original.a 1424 | } 1425 | } 1426 | return false; 1427 | }, 1428 | 1429 | write: function(color) { 1430 | return { 1431 | r: color.r, 1432 | g: color.g, 1433 | b: color.b, 1434 | a: color.a 1435 | } 1436 | } 1437 | }, 1438 | 1439 | RGB_OBJ: { 1440 | read: function(original) { 1441 | if (common.isNumber(original.r) && 1442 | common.isNumber(original.g) && 1443 | common.isNumber(original.b)) { 1444 | return { 1445 | space: 'RGB', 1446 | r: original.r, 1447 | g: original.g, 1448 | b: original.b 1449 | } 1450 | } 1451 | return false; 1452 | }, 1453 | 1454 | write: function(color) { 1455 | return { 1456 | r: color.r, 1457 | g: color.g, 1458 | b: color.b 1459 | } 1460 | } 1461 | }, 1462 | 1463 | HSVA_OBJ: { 1464 | read: function(original) { 1465 | if (common.isNumber(original.h) && 1466 | common.isNumber(original.s) && 1467 | common.isNumber(original.v) && 1468 | common.isNumber(original.a)) { 1469 | return { 1470 | space: 'HSV', 1471 | h: original.h, 1472 | s: original.s, 1473 | v: original.v, 1474 | a: original.a 1475 | } 1476 | } 1477 | return false; 1478 | }, 1479 | 1480 | write: function(color) { 1481 | return { 1482 | h: color.h, 1483 | s: color.s, 1484 | v: color.v, 1485 | a: color.a 1486 | } 1487 | } 1488 | }, 1489 | 1490 | HSV_OBJ: { 1491 | read: function(original) { 1492 | if (common.isNumber(original.h) && 1493 | common.isNumber(original.s) && 1494 | common.isNumber(original.v)) { 1495 | return { 1496 | space: 'HSV', 1497 | h: original.h, 1498 | s: original.s, 1499 | v: original.v 1500 | } 1501 | } 1502 | return false; 1503 | }, 1504 | 1505 | write: function(color) { 1506 | return { 1507 | h: color.h, 1508 | s: color.s, 1509 | v: color.v 1510 | } 1511 | } 1512 | 1513 | } 1514 | 1515 | } 1516 | 1517 | } 1518 | 1519 | 1520 | ]; 1521 | 1522 | return interpret; 1523 | 1524 | 1525 | })(dat.color.toString, 1526 | dat.utils.common); 1527 | 1528 | 1529 | dat.GUI = dat.gui.GUI = (function (css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) { 1530 | 1531 | css.inject(styleSheet); 1532 | 1533 | /** Outer-most className for GUI's */ 1534 | var CSS_NAMESPACE = 'dg'; 1535 | 1536 | var HIDE_KEY_CODE = 72; 1537 | 1538 | /** The only value shared between the JS and SCSS. Use caution. */ 1539 | var CLOSE_BUTTON_HEIGHT = 20; 1540 | 1541 | var DEFAULT_DEFAULT_PRESET_NAME = 'Default'; 1542 | 1543 | var SUPPORTS_LOCAL_STORAGE = (function() { 1544 | try { 1545 | return 'localStorage' in window && window['localStorage'] !== null; 1546 | } catch (e) { 1547 | return false; 1548 | } 1549 | })(); 1550 | 1551 | var SAVE_DIALOGUE; 1552 | 1553 | /** Have we yet to create an autoPlace GUI? */ 1554 | var auto_place_virgin = true; 1555 | 1556 | /** Fixed position div that auto place GUI's go inside */ 1557 | var auto_place_container; 1558 | 1559 | /** Are we hiding the GUI's ? */ 1560 | var hide = false; 1561 | 1562 | /** GUI's which should be hidden */ 1563 | var hideable_guis = []; 1564 | 1565 | /** 1566 | * A lightweight controller library for JavaScript. It allows you to easily 1567 | * manipulate variables and fire functions on the fly. 1568 | * @class 1569 | * 1570 | * @member dat.gui 1571 | * 1572 | * @param {Object} [params] 1573 | * @param {String} [params.name] The name of this GUI. 1574 | * @param {Object} [params.load] JSON object representing the saved state of 1575 | * this GUI. 1576 | * @param {Boolean} [params.auto=true] 1577 | * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in. 1578 | * @param {Boolean} [params.closed] If true, starts closed 1579 | */ 1580 | var GUI = function(params) { 1581 | 1582 | var _this = this; 1583 | 1584 | /** 1585 | * Outermost DOM Element 1586 | * @type DOMElement 1587 | */ 1588 | this.domElement = document.createElement('div'); 1589 | this.__ul = document.createElement('ul'); 1590 | this.domElement.appendChild(this.__ul); 1591 | 1592 | dom.addClass(this.domElement, CSS_NAMESPACE); 1593 | 1594 | /** 1595 | * Nested GUI's by name 1596 | * @ignore 1597 | */ 1598 | this.__folders = {}; 1599 | 1600 | this.__controllers = []; 1601 | 1602 | /** 1603 | * List of objects I'm remembering for save, only used in top level GUI 1604 | * @ignore 1605 | */ 1606 | this.__rememberedObjects = []; 1607 | 1608 | /** 1609 | * Maps the index of remembered objects to a map of controllers, only used 1610 | * in top level GUI. 1611 | * 1612 | * @private 1613 | * @ignore 1614 | * 1615 | * @example 1616 | * [ 1617 | * { 1618 | * propertyName: Controller, 1619 | * anotherPropertyName: Controller 1620 | * }, 1621 | * { 1622 | * propertyName: Controller 1623 | * } 1624 | * ] 1625 | */ 1626 | this.__rememberedObjectIndecesToControllers = []; 1627 | 1628 | this.__listening = []; 1629 | 1630 | params = params || {}; 1631 | 1632 | // Default parameters 1633 | params = common.defaults(params, { 1634 | autoPlace: true, 1635 | width: GUI.DEFAULT_WIDTH 1636 | }); 1637 | 1638 | params = common.defaults(params, { 1639 | resizable: params.autoPlace, 1640 | hideable: params.autoPlace 1641 | }); 1642 | 1643 | 1644 | if (!common.isUndefined(params.load)) { 1645 | 1646 | // Explicit preset 1647 | if (params.preset) params.load.preset = params.preset; 1648 | 1649 | } else { 1650 | 1651 | params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME }; 1652 | 1653 | } 1654 | 1655 | if (common.isUndefined(params.parent) && params.hideable) { 1656 | hideable_guis.push(this); 1657 | } 1658 | 1659 | // Only root level GUI's are resizable. 1660 | params.resizable = common.isUndefined(params.parent) && params.resizable; 1661 | 1662 | 1663 | if (params.autoPlace && common.isUndefined(params.scrollable)) { 1664 | params.scrollable = true; 1665 | } 1666 | // params.scrollable = common.isUndefined(params.parent) && params.scrollable === true; 1667 | 1668 | // Not part of params because I don't want people passing this in via 1669 | // constructor. Should be a 'remembered' value. 1670 | var use_local_storage = 1671 | SUPPORTS_LOCAL_STORAGE && 1672 | localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true'; 1673 | 1674 | Object.defineProperties(this, 1675 | 1676 | /** @lends dat.gui.GUI.prototype */ 1677 | { 1678 | 1679 | /** 1680 | * The parent GUI 1681 | * @type dat.gui.GUI 1682 | */ 1683 | parent: { 1684 | get: function() { 1685 | return params.parent; 1686 | } 1687 | }, 1688 | 1689 | scrollable: { 1690 | get: function() { 1691 | return params.scrollable; 1692 | } 1693 | }, 1694 | 1695 | /** 1696 | * Handles GUI's element placement for you 1697 | * @type Boolean 1698 | */ 1699 | autoPlace: { 1700 | get: function() { 1701 | return params.autoPlace; 1702 | } 1703 | }, 1704 | 1705 | /** 1706 | * The identifier for a set of saved values 1707 | * @type String 1708 | */ 1709 | preset: { 1710 | 1711 | get: function() { 1712 | if (_this.parent) { 1713 | return _this.getRoot().preset; 1714 | } else { 1715 | return params.load.preset; 1716 | } 1717 | }, 1718 | 1719 | set: function(v) { 1720 | if (_this.parent) { 1721 | _this.getRoot().preset = v; 1722 | } else { 1723 | params.load.preset = v; 1724 | } 1725 | setPresetSelectIndex(this); 1726 | _this.revert(); 1727 | } 1728 | 1729 | }, 1730 | 1731 | /** 1732 | * The width of GUI element 1733 | * @type Number 1734 | */ 1735 | width: { 1736 | get: function() { 1737 | return params.width; 1738 | }, 1739 | set: function(v) { 1740 | params.width = v; 1741 | setWidth(_this, v); 1742 | } 1743 | }, 1744 | 1745 | /** 1746 | * The name of GUI. Used for folders. i.e 1747 | * a folder's name 1748 | * @type String 1749 | */ 1750 | name: { 1751 | get: function() { 1752 | return params.name; 1753 | }, 1754 | set: function(v) { 1755 | // TODO Check for collisions among sibling folders 1756 | params.name = v; 1757 | if (title_row_name) { 1758 | title_row_name.innerHTML = params.name; 1759 | } 1760 | } 1761 | }, 1762 | 1763 | /** 1764 | * Whether the GUI is collapsed or not 1765 | * @type Boolean 1766 | */ 1767 | closed: { 1768 | get: function() { 1769 | return params.closed; 1770 | }, 1771 | set: function(v) { 1772 | params.closed = v; 1773 | if (params.closed) { 1774 | dom.addClass(_this.__ul, GUI.CLASS_CLOSED); 1775 | } else { 1776 | dom.removeClass(_this.__ul, GUI.CLASS_CLOSED); 1777 | } 1778 | // For browsers that aren't going to respect the CSS transition, 1779 | // Lets just check our height against the window height right off 1780 | // the bat. 1781 | this.onResize(); 1782 | 1783 | if (_this.__closeButton) { 1784 | _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED; 1785 | } 1786 | } 1787 | }, 1788 | 1789 | /** 1790 | * Contains all presets 1791 | * @type Object 1792 | */ 1793 | load: { 1794 | get: function() { 1795 | return params.load; 1796 | } 1797 | }, 1798 | 1799 | /** 1800 | * Determines whether or not to use localStorage as the means for 1801 | * remembering 1802 | * @type Boolean 1803 | */ 1804 | useLocalStorage: { 1805 | 1806 | get: function() { 1807 | return use_local_storage; 1808 | }, 1809 | set: function(bool) { 1810 | if (SUPPORTS_LOCAL_STORAGE) { 1811 | use_local_storage = bool; 1812 | if (bool) { 1813 | dom.bind(window, 'unload', saveToLocalStorage); 1814 | } else { 1815 | dom.unbind(window, 'unload', saveToLocalStorage); 1816 | } 1817 | localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool); 1818 | } 1819 | } 1820 | 1821 | } 1822 | 1823 | }); 1824 | 1825 | // Are we a root level GUI? 1826 | if (common.isUndefined(params.parent)) { 1827 | 1828 | params.closed = false; 1829 | 1830 | dom.addClass(this.domElement, GUI.CLASS_MAIN); 1831 | dom.makeSelectable(this.domElement, false); 1832 | 1833 | // Are we supposed to be loading locally? 1834 | if (SUPPORTS_LOCAL_STORAGE) { 1835 | 1836 | if (use_local_storage) { 1837 | 1838 | _this.useLocalStorage = true; 1839 | 1840 | var saved_gui = localStorage.getItem(getLocalStorageHash(this, 'gui')); 1841 | 1842 | if (saved_gui) { 1843 | params.load = JSON.parse(saved_gui); 1844 | } 1845 | 1846 | } 1847 | 1848 | } 1849 | 1850 | this.__closeButton = document.createElement('div'); 1851 | this.__closeButton.innerHTML = GUI.TEXT_CLOSED; 1852 | dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON); 1853 | this.domElement.appendChild(this.__closeButton); 1854 | 1855 | dom.bind(this.__closeButton, 'click', function() { 1856 | 1857 | _this.closed = !_this.closed; 1858 | 1859 | 1860 | }); 1861 | 1862 | 1863 | // Oh, you're a nested GUI! 1864 | } else { 1865 | 1866 | if (params.closed === undefined) { 1867 | params.closed = true; 1868 | } 1869 | 1870 | var title_row_name = document.createTextNode(params.name); 1871 | dom.addClass(title_row_name, 'controller-name'); 1872 | 1873 | var title_row = addRow(_this, title_row_name); 1874 | 1875 | var on_click_title = function(e) { 1876 | e.preventDefault(); 1877 | _this.closed = !_this.closed; 1878 | return false; 1879 | }; 1880 | 1881 | dom.addClass(this.__ul, GUI.CLASS_CLOSED); 1882 | 1883 | dom.addClass(title_row, 'title'); 1884 | dom.bind(title_row, 'click', on_click_title); 1885 | 1886 | if (!params.closed) { 1887 | this.closed = false; 1888 | } 1889 | 1890 | } 1891 | 1892 | if (params.autoPlace) { 1893 | 1894 | if (common.isUndefined(params.parent)) { 1895 | 1896 | if (auto_place_virgin) { 1897 | auto_place_container = document.createElement('div'); 1898 | dom.addClass(auto_place_container, CSS_NAMESPACE); 1899 | dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER); 1900 | document.body.appendChild(auto_place_container); 1901 | auto_place_virgin = false; 1902 | } 1903 | 1904 | // Put it in the dom for you. 1905 | auto_place_container.appendChild(this.domElement); 1906 | 1907 | // Apply the auto styles 1908 | dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE); 1909 | 1910 | } 1911 | 1912 | 1913 | // Make it not elastic. 1914 | if (!this.parent) setWidth(_this, params.width); 1915 | 1916 | } 1917 | 1918 | dom.bind(window, 'resize', function() { _this.onResize() }); 1919 | dom.bind(this.__ul, 'webkitTransitionEnd', function() { _this.onResize(); }); 1920 | dom.bind(this.__ul, 'transitionend', function() { _this.onResize() }); 1921 | dom.bind(this.__ul, 'oTransitionEnd', function() { _this.onResize() }); 1922 | this.onResize(); 1923 | 1924 | 1925 | if (params.resizable) { 1926 | addResizeHandle(this); 1927 | } 1928 | 1929 | function saveToLocalStorage() { 1930 | localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject())); 1931 | } 1932 | 1933 | var root = _this.getRoot(); 1934 | function resetWidth() { 1935 | var root = _this.getRoot(); 1936 | root.width += 1; 1937 | common.defer(function() { 1938 | root.width -= 1; 1939 | }); 1940 | } 1941 | 1942 | if (!params.parent) { 1943 | resetWidth(); 1944 | } 1945 | 1946 | }; 1947 | 1948 | GUI.toggleHide = function() { 1949 | 1950 | hide = !hide; 1951 | common.each(hideable_guis, function(gui) { 1952 | gui.domElement.style.zIndex = hide ? -999 : 999; 1953 | gui.domElement.style.opacity = hide ? 0 : 1; 1954 | }); 1955 | }; 1956 | 1957 | GUI.CLASS_AUTO_PLACE = 'a'; 1958 | GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac'; 1959 | GUI.CLASS_MAIN = 'main'; 1960 | GUI.CLASS_CONTROLLER_ROW = 'cr'; 1961 | GUI.CLASS_TOO_TALL = 'taller-than-window'; 1962 | GUI.CLASS_CLOSED = 'closed'; 1963 | GUI.CLASS_CLOSE_BUTTON = 'close-button'; 1964 | GUI.CLASS_DRAG = 'drag'; 1965 | 1966 | GUI.DEFAULT_WIDTH = 245; 1967 | GUI.TEXT_CLOSED = 'Close Controls'; 1968 | GUI.TEXT_OPEN = 'Open Controls'; 1969 | 1970 | dom.bind(window, 'keydown', function(e) { 1971 | 1972 | if (document.activeElement.type !== 'text' && 1973 | (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) { 1974 | GUI.toggleHide(); 1975 | } 1976 | 1977 | }, false); 1978 | 1979 | common.extend( 1980 | 1981 | GUI.prototype, 1982 | 1983 | /** @lends dat.gui.GUI */ 1984 | { 1985 | 1986 | /** 1987 | * @param object 1988 | * @param property 1989 | * @returns {dat.controllers.Controller} The new controller that was added. 1990 | * @instance 1991 | */ 1992 | add: function(object, property) { 1993 | 1994 | return add( 1995 | this, 1996 | object, 1997 | property, 1998 | { 1999 | factoryArgs: Array.prototype.slice.call(arguments, 2) 2000 | } 2001 | ); 2002 | 2003 | }, 2004 | 2005 | /** 2006 | * @param object 2007 | * @param property 2008 | * @returns {dat.controllers.ColorController} The new controller that was added. 2009 | * @instance 2010 | */ 2011 | addColor: function(object, property) { 2012 | 2013 | return add( 2014 | this, 2015 | object, 2016 | property, 2017 | { 2018 | color: true 2019 | } 2020 | ); 2021 | 2022 | }, 2023 | 2024 | /** 2025 | * @param controller 2026 | * @instance 2027 | */ 2028 | remove: function(controller) { 2029 | 2030 | // TODO listening? 2031 | this.__ul.removeChild(controller.__li); 2032 | this.__controllers.slice(this.__controllers.indexOf(controller), 1); 2033 | var _this = this; 2034 | common.defer(function() { 2035 | _this.onResize(); 2036 | }); 2037 | 2038 | }, 2039 | 2040 | destroy: function() { 2041 | 2042 | if (this.autoPlace) { 2043 | auto_place_container.removeChild(this.domElement); 2044 | } 2045 | 2046 | }, 2047 | 2048 | /** 2049 | * @param name 2050 | * @returns {dat.gui.GUI} The new folder. 2051 | * @throws {Error} if this GUI already has a folder by the specified 2052 | * name 2053 | * @instance 2054 | */ 2055 | addFolder: function(name) { 2056 | 2057 | // We have to prevent collisions on names in order to have a key 2058 | // by which to remember saved values 2059 | if (this.__folders[name] !== undefined) { 2060 | throw new Error('You already have a folder in this GUI by the' + 2061 | ' name "' + name + '"'); 2062 | } 2063 | 2064 | var new_gui_params = { name: name, parent: this }; 2065 | 2066 | // We need to pass down the autoPlace trait so that we can 2067 | // attach event listeners to open/close folder actions to 2068 | // ensure that a scrollbar appears if the window is too short. 2069 | new_gui_params.autoPlace = this.autoPlace; 2070 | 2071 | // Do we have saved appearance data for this folder? 2072 | 2073 | if (this.load && // Anything loaded? 2074 | this.load.folders && // Was my parent a dead-end? 2075 | this.load.folders[name]) { // Did daddy remember me? 2076 | 2077 | // Start me closed if I was closed 2078 | new_gui_params.closed = this.load.folders[name].closed; 2079 | 2080 | // Pass down the loaded data 2081 | new_gui_params.load = this.load.folders[name]; 2082 | 2083 | } 2084 | 2085 | var gui = new GUI(new_gui_params); 2086 | this.__folders[name] = gui; 2087 | 2088 | var li = addRow(this, gui.domElement); 2089 | dom.addClass(li, 'folder'); 2090 | return gui; 2091 | 2092 | }, 2093 | 2094 | open: function() { 2095 | this.closed = false; 2096 | }, 2097 | 2098 | close: function() { 2099 | this.closed = true; 2100 | }, 2101 | 2102 | onResize: function() { 2103 | 2104 | var root = this.getRoot(); 2105 | 2106 | if (root.scrollable) { 2107 | 2108 | var top = dom.getOffset(root.__ul).top; 2109 | var h = 0; 2110 | 2111 | common.each(root.__ul.childNodes, function(node) { 2112 | if (! (root.autoPlace && node === root.__save_row)) 2113 | h += dom.getHeight(node); 2114 | }); 2115 | 2116 | if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) { 2117 | dom.addClass(root.domElement, GUI.CLASS_TOO_TALL); 2118 | root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px'; 2119 | } else { 2120 | dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL); 2121 | root.__ul.style.height = 'auto'; 2122 | } 2123 | 2124 | } 2125 | 2126 | if (root.__resize_handle) { 2127 | common.defer(function() { 2128 | root.__resize_handle.style.height = root.__ul.offsetHeight + 'px'; 2129 | }); 2130 | } 2131 | 2132 | if (root.__closeButton) { 2133 | root.__closeButton.style.width = root.width + 'px'; 2134 | } 2135 | 2136 | }, 2137 | 2138 | /** 2139 | * Mark objects for saving. The order of these objects cannot change as 2140 | * the GUI grows. When remembering new objects, append them to the end 2141 | * of the list. 2142 | * 2143 | * @param {Object...} objects 2144 | * @throws {Error} if not called on a top level GUI. 2145 | * @instance 2146 | */ 2147 | remember: function() { 2148 | 2149 | if (common.isUndefined(SAVE_DIALOGUE)) { 2150 | SAVE_DIALOGUE = new CenteredDiv(); 2151 | SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents; 2152 | } 2153 | 2154 | if (this.parent) { 2155 | throw new Error("You can only call remember on a top level GUI."); 2156 | } 2157 | 2158 | var _this = this; 2159 | 2160 | common.each(Array.prototype.slice.call(arguments), function(object) { 2161 | if (_this.__rememberedObjects.length == 0) { 2162 | addSaveMenu(_this); 2163 | } 2164 | if (_this.__rememberedObjects.indexOf(object) == -1) { 2165 | _this.__rememberedObjects.push(object); 2166 | } 2167 | }); 2168 | 2169 | if (this.autoPlace) { 2170 | // Set save row width 2171 | setWidth(this, this.width); 2172 | } 2173 | 2174 | }, 2175 | 2176 | /** 2177 | * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI. 2178 | * @instance 2179 | */ 2180 | getRoot: function() { 2181 | var gui = this; 2182 | while (gui.parent) { 2183 | gui = gui.parent; 2184 | } 2185 | return gui; 2186 | }, 2187 | 2188 | /** 2189 | * @returns {Object} a JSON object representing the current state of 2190 | * this GUI as well as its remembered properties. 2191 | * @instance 2192 | */ 2193 | getSaveObject: function() { 2194 | 2195 | var toReturn = this.load; 2196 | 2197 | toReturn.closed = this.closed; 2198 | 2199 | // Am I remembering any values? 2200 | if (this.__rememberedObjects.length > 0) { 2201 | 2202 | toReturn.preset = this.preset; 2203 | 2204 | if (!toReturn.remembered) { 2205 | toReturn.remembered = {}; 2206 | } 2207 | 2208 | toReturn.remembered[this.preset] = getCurrentPreset(this); 2209 | 2210 | } 2211 | 2212 | toReturn.folders = {}; 2213 | common.each(this.__folders, function(element, key) { 2214 | toReturn.folders[key] = element.getSaveObject(); 2215 | }); 2216 | 2217 | return toReturn; 2218 | 2219 | }, 2220 | 2221 | save: function() { 2222 | 2223 | if (!this.load.remembered) { 2224 | this.load.remembered = {}; 2225 | } 2226 | 2227 | this.load.remembered[this.preset] = getCurrentPreset(this); 2228 | markPresetModified(this, false); 2229 | 2230 | }, 2231 | 2232 | saveAs: function(presetName) { 2233 | 2234 | if (!this.load.remembered) { 2235 | 2236 | // Retain default values upon first save 2237 | this.load.remembered = {}; 2238 | this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true); 2239 | 2240 | } 2241 | 2242 | this.load.remembered[presetName] = getCurrentPreset(this); 2243 | this.preset = presetName; 2244 | addPresetOption(this, presetName, true); 2245 | 2246 | }, 2247 | 2248 | revert: function(gui) { 2249 | 2250 | common.each(this.__controllers, function(controller) { 2251 | // Make revert work on Default. 2252 | if (!this.getRoot().load.remembered) { 2253 | controller.setValue(controller.initialValue); 2254 | } else { 2255 | recallSavedValue(gui || this.getRoot(), controller); 2256 | } 2257 | }, this); 2258 | 2259 | common.each(this.__folders, function(folder) { 2260 | folder.revert(folder); 2261 | }); 2262 | 2263 | if (!gui) { 2264 | markPresetModified(this.getRoot(), false); 2265 | } 2266 | 2267 | 2268 | }, 2269 | 2270 | listen: function(controller) { 2271 | 2272 | var init = this.__listening.length == 0; 2273 | this.__listening.push(controller); 2274 | if (init) updateDisplays(this.__listening); 2275 | 2276 | } 2277 | 2278 | } 2279 | 2280 | ); 2281 | 2282 | function add(gui, object, property, params) { 2283 | 2284 | if (object[property] === undefined) { 2285 | throw new Error("Object " + object + " has no property \"" + property + "\""); 2286 | } 2287 | 2288 | var controller; 2289 | 2290 | if (params.color) { 2291 | 2292 | controller = new ColorController(object, property); 2293 | 2294 | } else { 2295 | 2296 | var factoryArgs = [object,property].concat(params.factoryArgs); 2297 | controller = controllerFactory.apply(gui, factoryArgs); 2298 | 2299 | } 2300 | 2301 | if (params.before instanceof Controller) { 2302 | params.before = params.before.__li; 2303 | } 2304 | 2305 | recallSavedValue(gui, controller); 2306 | 2307 | dom.addClass(controller.domElement, 'c'); 2308 | 2309 | var name = document.createElement('span'); 2310 | dom.addClass(name, 'property-name'); 2311 | name.innerHTML = controller.property; 2312 | 2313 | var container = document.createElement('div'); 2314 | container.appendChild(name); 2315 | container.appendChild(controller.domElement); 2316 | 2317 | var li = addRow(gui, container, params.before); 2318 | 2319 | dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); 2320 | dom.addClass(li, typeof controller.getValue()); 2321 | 2322 | augmentController(gui, li, controller); 2323 | 2324 | gui.__controllers.push(controller); 2325 | 2326 | return controller; 2327 | 2328 | } 2329 | 2330 | /** 2331 | * Add a row to the end of the GUI or before another row. 2332 | * 2333 | * @param gui 2334 | * @param [dom] If specified, inserts the dom content in the new row 2335 | * @param [liBefore] If specified, places the new row before another row 2336 | */ 2337 | function addRow(gui, dom, liBefore) { 2338 | var li = document.createElement('li'); 2339 | if (dom) li.appendChild(dom); 2340 | if (liBefore) { 2341 | gui.__ul.insertBefore(li, params.before); 2342 | } else { 2343 | gui.__ul.appendChild(li); 2344 | } 2345 | gui.onResize(); 2346 | return li; 2347 | } 2348 | 2349 | function augmentController(gui, li, controller) { 2350 | 2351 | controller.__li = li; 2352 | controller.__gui = gui; 2353 | 2354 | common.extend(controller, { 2355 | 2356 | options: function(options) { 2357 | 2358 | if (arguments.length > 1) { 2359 | controller.remove(); 2360 | 2361 | return add( 2362 | gui, 2363 | controller.object, 2364 | controller.property, 2365 | { 2366 | before: controller.__li.nextElementSibling, 2367 | factoryArgs: [common.toArray(arguments)] 2368 | } 2369 | ); 2370 | 2371 | } 2372 | 2373 | if (common.isArray(options) || common.isObject(options)) { 2374 | controller.remove(); 2375 | 2376 | return add( 2377 | gui, 2378 | controller.object, 2379 | controller.property, 2380 | { 2381 | before: controller.__li.nextElementSibling, 2382 | factoryArgs: [options] 2383 | } 2384 | ); 2385 | 2386 | } 2387 | 2388 | }, 2389 | 2390 | name: function(v) { 2391 | controller.__li.firstElementChild.firstElementChild.innerHTML = v; 2392 | return controller; 2393 | }, 2394 | 2395 | listen: function() { 2396 | controller.__gui.listen(controller); 2397 | return controller; 2398 | }, 2399 | 2400 | remove: function() { 2401 | controller.__gui.remove(controller); 2402 | return controller; 2403 | } 2404 | 2405 | }); 2406 | 2407 | // All sliders should be accompanied by a box. 2408 | if (controller instanceof NumberControllerSlider) { 2409 | 2410 | var box = new NumberControllerBox(controller.object, controller.property, 2411 | { min: controller.__min, max: controller.__max, step: controller.__step }); 2412 | 2413 | common.each(['updateDisplay', 'onChange', 'onFinishChange'], function(method) { 2414 | var pc = controller[method]; 2415 | var pb = box[method]; 2416 | controller[method] = box[method] = function() { 2417 | var args = Array.prototype.slice.call(arguments); 2418 | pc.apply(controller, args); 2419 | return pb.apply(box, args); 2420 | } 2421 | }); 2422 | 2423 | dom.addClass(li, 'has-slider'); 2424 | controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild); 2425 | 2426 | } 2427 | else if (controller instanceof NumberControllerBox) { 2428 | 2429 | var r = function(returned) { 2430 | 2431 | // Have we defined both boundaries? 2432 | if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) { 2433 | 2434 | // Well, then lets just replace this with a slider. 2435 | controller.remove(); 2436 | return add( 2437 | gui, 2438 | controller.object, 2439 | controller.property, 2440 | { 2441 | before: controller.__li.nextElementSibling, 2442 | factoryArgs: [controller.__min, controller.__max, controller.__step] 2443 | }); 2444 | 2445 | } 2446 | 2447 | return returned; 2448 | 2449 | }; 2450 | 2451 | controller.min = common.compose(r, controller.min); 2452 | controller.max = common.compose(r, controller.max); 2453 | 2454 | } 2455 | else if (controller instanceof BooleanController) { 2456 | 2457 | dom.bind(li, 'click', function() { 2458 | dom.fakeEvent(controller.__checkbox, 'click'); 2459 | }); 2460 | 2461 | dom.bind(controller.__checkbox, 'click', function(e) { 2462 | e.stopPropagation(); // Prevents double-toggle 2463 | }) 2464 | 2465 | } 2466 | else if (controller instanceof FunctionController) { 2467 | 2468 | dom.bind(li, 'click', function() { 2469 | dom.fakeEvent(controller.__button, 'click'); 2470 | }); 2471 | 2472 | dom.bind(li, 'mouseover', function() { 2473 | dom.addClass(controller.__button, 'hover'); 2474 | }); 2475 | 2476 | dom.bind(li, 'mouseout', function() { 2477 | dom.removeClass(controller.__button, 'hover'); 2478 | }); 2479 | 2480 | } 2481 | else if (controller instanceof ColorController) { 2482 | 2483 | dom.addClass(li, 'color'); 2484 | controller.updateDisplay = common.compose(function(r) { 2485 | li.style.borderLeftColor = controller.__color.toString(); 2486 | return r; 2487 | }, controller.updateDisplay); 2488 | 2489 | controller.updateDisplay(); 2490 | 2491 | } 2492 | 2493 | controller.setValue = common.compose(function(r) { 2494 | if (gui.getRoot().__preset_select && controller.isModified()) { 2495 | markPresetModified(gui.getRoot(), true); 2496 | } 2497 | return r; 2498 | }, controller.setValue); 2499 | 2500 | } 2501 | 2502 | function recallSavedValue(gui, controller) { 2503 | 2504 | // Find the topmost GUI, that's where remembered objects live. 2505 | var root = gui.getRoot(); 2506 | 2507 | // Does the object we're controlling match anything we've been told to 2508 | // remember? 2509 | var matched_index = root.__rememberedObjects.indexOf(controller.object); 2510 | 2511 | // Why yes, it does! 2512 | if (matched_index != -1) { 2513 | 2514 | // Let me fetch a map of controllers for thcommon.isObject. 2515 | var controller_map = 2516 | root.__rememberedObjectIndecesToControllers[matched_index]; 2517 | 2518 | // Ohp, I believe this is the first controller we've created for this 2519 | // object. Lets make the map fresh. 2520 | if (controller_map === undefined) { 2521 | controller_map = {}; 2522 | root.__rememberedObjectIndecesToControllers[matched_index] = 2523 | controller_map; 2524 | } 2525 | 2526 | // Keep track of this controller 2527 | controller_map[controller.property] = controller; 2528 | 2529 | // Okay, now have we saved any values for this controller? 2530 | if (root.load && root.load.remembered) { 2531 | 2532 | var preset_map = root.load.remembered; 2533 | 2534 | // Which preset are we trying to load? 2535 | var preset; 2536 | 2537 | if (preset_map[gui.preset]) { 2538 | 2539 | preset = preset_map[gui.preset]; 2540 | 2541 | } else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) { 2542 | 2543 | // Uhh, you can have the default instead? 2544 | preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME]; 2545 | 2546 | } else { 2547 | 2548 | // Nada. 2549 | 2550 | return; 2551 | 2552 | } 2553 | 2554 | 2555 | // Did the loaded object remember thcommon.isObject? 2556 | if (preset[matched_index] && 2557 | 2558 | // Did we remember this particular property? 2559 | preset[matched_index][controller.property] !== undefined) { 2560 | 2561 | // We did remember something for this guy ... 2562 | var value = preset[matched_index][controller.property]; 2563 | 2564 | // And that's what it is. 2565 | controller.initialValue = value; 2566 | controller.setValue(value); 2567 | 2568 | } 2569 | 2570 | } 2571 | 2572 | } 2573 | 2574 | } 2575 | 2576 | function getLocalStorageHash(gui, key) { 2577 | // TODO how does this deal with multiple GUI's? 2578 | return document.location.href + '.' + key; 2579 | 2580 | } 2581 | 2582 | function addSaveMenu(gui) { 2583 | 2584 | var div = gui.__save_row = document.createElement('li'); 2585 | 2586 | dom.addClass(gui.domElement, 'has-save'); 2587 | 2588 | gui.__ul.insertBefore(div, gui.__ul.firstChild); 2589 | 2590 | dom.addClass(div, 'save-row'); 2591 | 2592 | var gears = document.createElement('span'); 2593 | gears.innerHTML = ' '; 2594 | dom.addClass(gears, 'button gears'); 2595 | 2596 | // TODO replace with FunctionController 2597 | var button = document.createElement('span'); 2598 | button.innerHTML = 'Save'; 2599 | dom.addClass(button, 'button'); 2600 | dom.addClass(button, 'save'); 2601 | 2602 | var button2 = document.createElement('span'); 2603 | button2.innerHTML = 'New'; 2604 | dom.addClass(button2, 'button'); 2605 | dom.addClass(button2, 'save-as'); 2606 | 2607 | var button3 = document.createElement('span'); 2608 | button3.innerHTML = 'Revert'; 2609 | dom.addClass(button3, 'button'); 2610 | dom.addClass(button3, 'revert'); 2611 | 2612 | var select = gui.__preset_select = document.createElement('select'); 2613 | 2614 | if (gui.load && gui.load.remembered) { 2615 | 2616 | common.each(gui.load.remembered, function(value, key) { 2617 | addPresetOption(gui, key, key == gui.preset); 2618 | }); 2619 | 2620 | } else { 2621 | addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false); 2622 | } 2623 | 2624 | dom.bind(select, 'change', function() { 2625 | 2626 | 2627 | for (var index = 0; index < gui.__preset_select.length; index++) { 2628 | gui.__preset_select[index].innerHTML = gui.__preset_select[index].value; 2629 | } 2630 | 2631 | gui.preset = this.value; 2632 | 2633 | }); 2634 | 2635 | div.appendChild(select); 2636 | div.appendChild(gears); 2637 | div.appendChild(button); 2638 | div.appendChild(button2); 2639 | div.appendChild(button3); 2640 | 2641 | if (SUPPORTS_LOCAL_STORAGE) { 2642 | 2643 | var saveLocally = document.getElementById('dg-save-locally'); 2644 | var explain = document.getElementById('dg-local-explain'); 2645 | 2646 | saveLocally.style.display = 'block'; 2647 | 2648 | var localStorageCheckBox = document.getElementById('dg-local-storage'); 2649 | 2650 | if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') { 2651 | localStorageCheckBox.setAttribute('checked', 'checked'); 2652 | } 2653 | 2654 | function showHideExplain() { 2655 | explain.style.display = gui.useLocalStorage ? 'block' : 'none'; 2656 | } 2657 | 2658 | showHideExplain(); 2659 | 2660 | // TODO: Use a boolean controller, fool! 2661 | dom.bind(localStorageCheckBox, 'change', function() { 2662 | gui.useLocalStorage = !gui.useLocalStorage; 2663 | showHideExplain(); 2664 | }); 2665 | 2666 | } 2667 | 2668 | var newConstructorTextArea = document.getElementById('dg-new-constructor'); 2669 | 2670 | dom.bind(newConstructorTextArea, 'keydown', function(e) { 2671 | if (e.metaKey && (e.which === 67 || e.keyCode == 67)) { 2672 | SAVE_DIALOGUE.hide(); 2673 | } 2674 | }); 2675 | 2676 | dom.bind(gears, 'click', function() { 2677 | newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2); 2678 | SAVE_DIALOGUE.show(); 2679 | newConstructorTextArea.focus(); 2680 | newConstructorTextArea.select(); 2681 | }); 2682 | 2683 | dom.bind(button, 'click', function() { 2684 | gui.save(); 2685 | }); 2686 | 2687 | dom.bind(button2, 'click', function() { 2688 | var presetName = prompt('Enter a new preset name.'); 2689 | if (presetName) gui.saveAs(presetName); 2690 | }); 2691 | 2692 | dom.bind(button3, 'click', function() { 2693 | gui.revert(); 2694 | }); 2695 | 2696 | // div.appendChild(button2); 2697 | 2698 | } 2699 | 2700 | function addResizeHandle(gui) { 2701 | 2702 | gui.__resize_handle = document.createElement('div'); 2703 | 2704 | common.extend(gui.__resize_handle.style, { 2705 | 2706 | width: '6px', 2707 | marginLeft: '-3px', 2708 | height: '200px', 2709 | cursor: 'ew-resize', 2710 | position: 'absolute' 2711 | // border: '1px solid blue' 2712 | 2713 | }); 2714 | 2715 | var pmouseX; 2716 | 2717 | dom.bind(gui.__resize_handle, 'mousedown', dragStart); 2718 | dom.bind(gui.__closeButton, 'mousedown', dragStart); 2719 | 2720 | gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild); 2721 | 2722 | function dragStart(e) { 2723 | 2724 | e.preventDefault(); 2725 | 2726 | pmouseX = e.clientX; 2727 | 2728 | dom.addClass(gui.__closeButton, GUI.CLASS_DRAG); 2729 | dom.bind(window, 'mousemove', drag); 2730 | dom.bind(window, 'mouseup', dragStop); 2731 | 2732 | return false; 2733 | 2734 | } 2735 | 2736 | function drag(e) { 2737 | 2738 | e.preventDefault(); 2739 | 2740 | gui.width += pmouseX - e.clientX; 2741 | gui.onResize(); 2742 | pmouseX = e.clientX; 2743 | 2744 | return false; 2745 | 2746 | } 2747 | 2748 | function dragStop() { 2749 | 2750 | dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG); 2751 | dom.unbind(window, 'mousemove', drag); 2752 | dom.unbind(window, 'mouseup', dragStop); 2753 | 2754 | } 2755 | 2756 | } 2757 | 2758 | function setWidth(gui, w) { 2759 | gui.domElement.style.width = w + 'px'; 2760 | // Auto placed save-rows are position fixed, so we have to 2761 | // set the width manually if we want it to bleed to the edge 2762 | if (gui.__save_row && gui.autoPlace) { 2763 | gui.__save_row.style.width = w + 'px'; 2764 | }if (gui.__closeButton) { 2765 | gui.__closeButton.style.width = w + 'px'; 2766 | } 2767 | } 2768 | 2769 | function getCurrentPreset(gui, useInitialValues) { 2770 | 2771 | var toReturn = {}; 2772 | 2773 | // For each object I'm remembering 2774 | common.each(gui.__rememberedObjects, function(val, index) { 2775 | 2776 | var saved_values = {}; 2777 | 2778 | // The controllers I've made for thcommon.isObject by property 2779 | var controller_map = 2780 | gui.__rememberedObjectIndecesToControllers[index]; 2781 | 2782 | // Remember each value for each property 2783 | common.each(controller_map, function(controller, property) { 2784 | saved_values[property] = useInitialValues ? controller.initialValue : controller.getValue(); 2785 | }); 2786 | 2787 | // Save the values for thcommon.isObject 2788 | toReturn[index] = saved_values; 2789 | 2790 | }); 2791 | 2792 | return toReturn; 2793 | 2794 | } 2795 | 2796 | function addPresetOption(gui, name, setSelected) { 2797 | var opt = document.createElement('option'); 2798 | opt.innerHTML = name; 2799 | opt.value = name; 2800 | gui.__preset_select.appendChild(opt); 2801 | if (setSelected) { 2802 | gui.__preset_select.selectedIndex = gui.__preset_select.length - 1; 2803 | } 2804 | } 2805 | 2806 | function setPresetSelectIndex(gui) { 2807 | for (var index = 0; index < gui.__preset_select.length; index++) { 2808 | if (gui.__preset_select[index].value == gui.preset) { 2809 | gui.__preset_select.selectedIndex = index; 2810 | } 2811 | } 2812 | } 2813 | 2814 | function markPresetModified(gui, modified) { 2815 | var opt = gui.__preset_select[gui.__preset_select.selectedIndex]; 2816 | // console.log('mark', modified, opt); 2817 | if (modified) { 2818 | opt.innerHTML = opt.value + "*"; 2819 | } else { 2820 | opt.innerHTML = opt.value; 2821 | } 2822 | } 2823 | 2824 | function updateDisplays(controllerArray) { 2825 | 2826 | 2827 | if (controllerArray.length != 0) { 2828 | 2829 | requestAnimationFrame(function() { 2830 | updateDisplays(controllerArray); 2831 | }); 2832 | 2833 | } 2834 | 2835 | common.each(controllerArray, function(c) { 2836 | c.updateDisplay(); 2837 | }); 2838 | 2839 | } 2840 | 2841 | return GUI; 2842 | 2843 | })(dat.utils.css, 2844 | "
\n\n Here's the new load parameter for your GUI's constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI's constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
", 2845 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 2846 | dat.controllers.factory = (function (OptionController, NumberControllerBox, NumberControllerSlider, StringController, FunctionController, BooleanController, common) { 2847 | 2848 | return function(object, property) { 2849 | 2850 | var initialValue = object[property]; 2851 | 2852 | // Providing options? 2853 | if (common.isArray(arguments[2]) || common.isObject(arguments[2])) { 2854 | return new OptionController(object, property, arguments[2]); 2855 | } 2856 | 2857 | // Providing a map? 2858 | 2859 | if (common.isNumber(initialValue)) { 2860 | 2861 | if (common.isNumber(arguments[2]) && common.isNumber(arguments[3])) { 2862 | 2863 | // Has min and max. 2864 | return new NumberControllerSlider(object, property, arguments[2], arguments[3]); 2865 | 2866 | } else { 2867 | 2868 | return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] }); 2869 | 2870 | } 2871 | 2872 | } 2873 | 2874 | if (common.isString(initialValue)) { 2875 | return new StringController(object, property); 2876 | } 2877 | 2878 | if (common.isFunction(initialValue)) { 2879 | return new FunctionController(object, property, ''); 2880 | } 2881 | 2882 | if (common.isBoolean(initialValue)) { 2883 | return new BooleanController(object, property); 2884 | } 2885 | 2886 | } 2887 | 2888 | })(dat.controllers.OptionController, 2889 | dat.controllers.NumberControllerBox, 2890 | dat.controllers.NumberControllerSlider, 2891 | dat.controllers.StringController = (function (Controller, dom, common) { 2892 | 2893 | /** 2894 | * @class Provides a text input to alter the string property of an object. 2895 | * 2896 | * @extends dat.controllers.Controller 2897 | * 2898 | * @param {Object} object The object to be manipulated 2899 | * @param {string} property The name of the property to be manipulated 2900 | * 2901 | * @member dat.controllers 2902 | */ 2903 | var StringController = function(object, property) { 2904 | 2905 | StringController.superclass.call(this, object, property); 2906 | 2907 | var _this = this; 2908 | 2909 | this.__input = document.createElement('input'); 2910 | this.__input.setAttribute('type', 'text'); 2911 | 2912 | dom.bind(this.__input, 'keyup', onChange); 2913 | dom.bind(this.__input, 'change', onChange); 2914 | dom.bind(this.__input, 'blur', onBlur); 2915 | dom.bind(this.__input, 'keydown', function(e) { 2916 | if (e.keyCode === 13) { 2917 | this.blur(); 2918 | } 2919 | }); 2920 | 2921 | 2922 | function onChange() { 2923 | _this.setValue(_this.__input.value); 2924 | } 2925 | 2926 | function onBlur() { 2927 | if (_this.__onFinishChange) { 2928 | _this.__onFinishChange.call(_this, _this.getValue()); 2929 | } 2930 | } 2931 | 2932 | this.updateDisplay(); 2933 | 2934 | this.domElement.appendChild(this.__input); 2935 | 2936 | }; 2937 | 2938 | StringController.superclass = Controller; 2939 | 2940 | common.extend( 2941 | 2942 | StringController.prototype, 2943 | Controller.prototype, 2944 | 2945 | { 2946 | 2947 | updateDisplay: function() { 2948 | // Stops the caret from moving on account of: 2949 | // keyup -> setValue -> updateDisplay 2950 | if (!dom.isActive(this.__input)) { 2951 | this.__input.value = this.getValue(); 2952 | } 2953 | return StringController.superclass.prototype.updateDisplay.call(this); 2954 | } 2955 | 2956 | } 2957 | 2958 | ); 2959 | 2960 | return StringController; 2961 | 2962 | })(dat.controllers.Controller, 2963 | dat.dom.dom, 2964 | dat.utils.common), 2965 | dat.controllers.FunctionController, 2966 | dat.controllers.BooleanController, 2967 | dat.utils.common), 2968 | dat.controllers.Controller, 2969 | dat.controllers.BooleanController, 2970 | dat.controllers.FunctionController, 2971 | dat.controllers.NumberControllerBox, 2972 | dat.controllers.NumberControllerSlider, 2973 | dat.controllers.OptionController, 2974 | dat.controllers.ColorController = (function (Controller, dom, Color, interpret, common) { 2975 | 2976 | var ColorController = function(object, property) { 2977 | 2978 | ColorController.superclass.call(this, object, property); 2979 | 2980 | this.__color = new Color(this.getValue()); 2981 | this.__temp = new Color(0); 2982 | 2983 | var _this = this; 2984 | 2985 | this.domElement = document.createElement('div'); 2986 | 2987 | dom.makeSelectable(this.domElement, false); 2988 | 2989 | this.__selector = document.createElement('div'); 2990 | this.__selector.className = 'selector'; 2991 | 2992 | this.__saturation_field = document.createElement('div'); 2993 | this.__saturation_field.className = 'saturation-field'; 2994 | 2995 | this.__field_knob = document.createElement('div'); 2996 | this.__field_knob.className = 'field-knob'; 2997 | this.__field_knob_border = '2px solid '; 2998 | 2999 | this.__hue_knob = document.createElement('div'); 3000 | this.__hue_knob.className = 'hue-knob'; 3001 | 3002 | this.__hue_field = document.createElement('div'); 3003 | this.__hue_field.className = 'hue-field'; 3004 | 3005 | this.__input = document.createElement('input'); 3006 | this.__input.type = 'text'; 3007 | this.__input_textShadow = '0 1px 1px '; 3008 | 3009 | dom.bind(this.__input, 'keydown', function(e) { 3010 | if (e.keyCode === 13) { // on enter 3011 | onBlur.call(this); 3012 | } 3013 | }); 3014 | 3015 | dom.bind(this.__input, 'blur', onBlur); 3016 | 3017 | dom.bind(this.__selector, 'mousedown', function(e) { 3018 | 3019 | dom 3020 | .addClass(this, 'drag') 3021 | .bind(window, 'mouseup', function(e) { 3022 | dom.removeClass(_this.__selector, 'drag'); 3023 | }); 3024 | 3025 | }); 3026 | 3027 | var value_field = document.createElement('div'); 3028 | 3029 | common.extend(this.__selector.style, { 3030 | width: '122px', 3031 | height: '102px', 3032 | padding: '3px', 3033 | backgroundColor: '#222', 3034 | boxShadow: '0px 1px 3px rgba(0,0,0,0.3)' 3035 | }); 3036 | 3037 | common.extend(this.__field_knob.style, { 3038 | position: 'absolute', 3039 | width: '12px', 3040 | height: '12px', 3041 | border: this.__field_knob_border + (this.__color.v < .5 ? '#fff' : '#000'), 3042 | boxShadow: '0px 1px 3px rgba(0,0,0,0.5)', 3043 | borderRadius: '12px', 3044 | zIndex: 1 3045 | }); 3046 | 3047 | common.extend(this.__hue_knob.style, { 3048 | position: 'absolute', 3049 | width: '15px', 3050 | height: '2px', 3051 | borderRight: '4px solid #fff', 3052 | zIndex: 1 3053 | }); 3054 | 3055 | common.extend(this.__saturation_field.style, { 3056 | width: '100px', 3057 | height: '100px', 3058 | border: '1px solid #555', 3059 | marginRight: '3px', 3060 | display: 'inline-block', 3061 | cursor: 'pointer' 3062 | }); 3063 | 3064 | common.extend(value_field.style, { 3065 | width: '100%', 3066 | height: '100%', 3067 | background: 'none' 3068 | }); 3069 | 3070 | linearGradient(value_field, 'top', 'rgba(0,0,0,0)', '#000'); 3071 | 3072 | common.extend(this.__hue_field.style, { 3073 | width: '15px', 3074 | height: '100px', 3075 | display: 'inline-block', 3076 | border: '1px solid #555', 3077 | cursor: 'ns-resize' 3078 | }); 3079 | 3080 | hueGradient(this.__hue_field); 3081 | 3082 | common.extend(this.__input.style, { 3083 | outline: 'none', 3084 | // width: '120px', 3085 | textAlign: 'center', 3086 | // padding: '4px', 3087 | // marginBottom: '6px', 3088 | color: '#fff', 3089 | border: 0, 3090 | fontWeight: 'bold', 3091 | textShadow: this.__input_textShadow + 'rgba(0,0,0,0.7)' 3092 | }); 3093 | 3094 | dom.bind(this.__saturation_field, 'mousedown', fieldDown); 3095 | dom.bind(this.__field_knob, 'mousedown', fieldDown); 3096 | 3097 | dom.bind(this.__hue_field, 'mousedown', function(e) { 3098 | setH(e); 3099 | dom.bind(window, 'mousemove', setH); 3100 | dom.bind(window, 'mouseup', unbindH); 3101 | }); 3102 | 3103 | function fieldDown(e) { 3104 | setSV(e); 3105 | // document.body.style.cursor = 'none'; 3106 | dom.bind(window, 'mousemove', setSV); 3107 | dom.bind(window, 'mouseup', unbindSV); 3108 | } 3109 | 3110 | function unbindSV() { 3111 | dom.unbind(window, 'mousemove', setSV); 3112 | dom.unbind(window, 'mouseup', unbindSV); 3113 | // document.body.style.cursor = 'default'; 3114 | } 3115 | 3116 | function onBlur() { 3117 | var i = interpret(this.value); 3118 | if (i !== false) { 3119 | _this.__color.__state = i; 3120 | _this.setValue(_this.__color.toOriginal()); 3121 | } else { 3122 | this.value = _this.__color.toString(); 3123 | } 3124 | } 3125 | 3126 | function unbindH() { 3127 | dom.unbind(window, 'mousemove', setH); 3128 | dom.unbind(window, 'mouseup', unbindH); 3129 | } 3130 | 3131 | this.__saturation_field.appendChild(value_field); 3132 | this.__selector.appendChild(this.__field_knob); 3133 | this.__selector.appendChild(this.__saturation_field); 3134 | this.__selector.appendChild(this.__hue_field); 3135 | this.__hue_field.appendChild(this.__hue_knob); 3136 | 3137 | this.domElement.appendChild(this.__input); 3138 | this.domElement.appendChild(this.__selector); 3139 | 3140 | this.updateDisplay(); 3141 | 3142 | function setSV(e) { 3143 | 3144 | e.preventDefault(); 3145 | 3146 | var w = dom.getWidth(_this.__saturation_field); 3147 | var o = dom.getOffset(_this.__saturation_field); 3148 | var s = (e.clientX - o.left + document.body.scrollLeft) / w; 3149 | var v = 1 - (e.clientY - o.top + document.body.scrollTop) / w; 3150 | 3151 | if (v > 1) v = 1; 3152 | else if (v < 0) v = 0; 3153 | 3154 | if (s > 1) s = 1; 3155 | else if (s < 0) s = 0; 3156 | 3157 | _this.__color.v = v; 3158 | _this.__color.s = s; 3159 | 3160 | _this.setValue(_this.__color.toOriginal()); 3161 | 3162 | 3163 | return false; 3164 | 3165 | } 3166 | 3167 | function setH(e) { 3168 | 3169 | e.preventDefault(); 3170 | 3171 | var s = dom.getHeight(_this.__hue_field); 3172 | var o = dom.getOffset(_this.__hue_field); 3173 | var h = 1 - (e.clientY - o.top + document.body.scrollTop) / s; 3174 | 3175 | if (h > 1) h = 1; 3176 | else if (h < 0) h = 0; 3177 | 3178 | _this.__color.h = h * 360; 3179 | 3180 | _this.setValue(_this.__color.toOriginal()); 3181 | 3182 | return false; 3183 | 3184 | } 3185 | 3186 | }; 3187 | 3188 | ColorController.superclass = Controller; 3189 | 3190 | common.extend( 3191 | 3192 | ColorController.prototype, 3193 | Controller.prototype, 3194 | 3195 | { 3196 | 3197 | updateDisplay: function() { 3198 | 3199 | var i = interpret(this.getValue()); 3200 | 3201 | if (i !== false) { 3202 | 3203 | var mismatch = false; 3204 | 3205 | // Check for mismatch on the interpreted value. 3206 | 3207 | common.each(Color.COMPONENTS, function(component) { 3208 | if (!common.isUndefined(i[component]) && 3209 | !common.isUndefined(this.__color.__state[component]) && 3210 | i[component] !== this.__color.__state[component]) { 3211 | mismatch = true; 3212 | return {}; // break 3213 | } 3214 | }, this); 3215 | 3216 | // If nothing diverges, we keep our previous values 3217 | // for statefulness, otherwise we recalculate fresh 3218 | if (mismatch) { 3219 | common.extend(this.__color.__state, i); 3220 | } 3221 | 3222 | } 3223 | 3224 | common.extend(this.__temp.__state, this.__color.__state); 3225 | 3226 | this.__temp.a = 1; 3227 | 3228 | var flip = (this.__color.v < .5 || this.__color.s > .5) ? 255 : 0; 3229 | var _flip = 255 - flip; 3230 | 3231 | common.extend(this.__field_knob.style, { 3232 | marginLeft: 100 * this.__color.s - 7 + 'px', 3233 | marginTop: 100 * (1 - this.__color.v) - 7 + 'px', 3234 | backgroundColor: this.__temp.toString(), 3235 | border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip +')' 3236 | }); 3237 | 3238 | this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px' 3239 | 3240 | this.__temp.s = 1; 3241 | this.__temp.v = 1; 3242 | 3243 | linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toString()); 3244 | 3245 | common.extend(this.__input.style, { 3246 | backgroundColor: this.__input.value = this.__color.toString(), 3247 | color: 'rgb(' + flip + ',' + flip + ',' + flip +')', 3248 | textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip +',.7)' 3249 | }); 3250 | 3251 | } 3252 | 3253 | } 3254 | 3255 | ); 3256 | 3257 | var vendors = ['-moz-','-o-','-webkit-','-ms-','']; 3258 | 3259 | function linearGradient(elem, x, a, b) { 3260 | elem.style.background = ''; 3261 | common.each(vendors, function(vendor) { 3262 | elem.style.cssText += 'background: ' + vendor + 'linear-gradient('+x+', '+a+' 0%, ' + b + ' 100%); '; 3263 | }); 3264 | } 3265 | 3266 | function hueGradient(elem) { 3267 | elem.style.background = ''; 3268 | elem.style.cssText += 'background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);' 3269 | elem.style.cssText += 'background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3270 | elem.style.cssText += 'background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3271 | elem.style.cssText += 'background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3272 | elem.style.cssText += 'background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);' 3273 | } 3274 | 3275 | 3276 | return ColorController; 3277 | 3278 | })(dat.controllers.Controller, 3279 | dat.dom.dom, 3280 | dat.color.Color = (function (interpret, math, toString, common) { 3281 | 3282 | var Color = function() { 3283 | 3284 | this.__state = interpret.apply(this, arguments); 3285 | 3286 | if (this.__state === false) { 3287 | throw 'Failed to interpret color arguments'; 3288 | } 3289 | 3290 | this.__state.a = this.__state.a || 1; 3291 | 3292 | 3293 | }; 3294 | 3295 | Color.COMPONENTS = ['r','g','b','h','s','v','hex','a']; 3296 | 3297 | common.extend(Color.prototype, { 3298 | 3299 | toString: function() { 3300 | return toString(this); 3301 | }, 3302 | 3303 | toOriginal: function() { 3304 | return this.__state.conversion.write(this); 3305 | } 3306 | 3307 | }); 3308 | 3309 | defineRGBComponent(Color.prototype, 'r', 2); 3310 | defineRGBComponent(Color.prototype, 'g', 1); 3311 | defineRGBComponent(Color.prototype, 'b', 0); 3312 | 3313 | defineHSVComponent(Color.prototype, 'h'); 3314 | defineHSVComponent(Color.prototype, 's'); 3315 | defineHSVComponent(Color.prototype, 'v'); 3316 | 3317 | Object.defineProperty(Color.prototype, 'a', { 3318 | 3319 | get: function() { 3320 | return this.__state.a; 3321 | }, 3322 | 3323 | set: function(v) { 3324 | this.__state.a = v; 3325 | } 3326 | 3327 | }); 3328 | 3329 | Object.defineProperty(Color.prototype, 'hex', { 3330 | 3331 | get: function() { 3332 | 3333 | if (!this.__state.space !== 'HEX') { 3334 | this.__state.hex = math.rgb_to_hex(this.r, this.g, this.b); 3335 | } 3336 | 3337 | return this.__state.hex; 3338 | 3339 | }, 3340 | 3341 | set: function(v) { 3342 | 3343 | this.__state.space = 'HEX'; 3344 | this.__state.hex = v; 3345 | 3346 | } 3347 | 3348 | }); 3349 | 3350 | function defineRGBComponent(target, component, componentHexIndex) { 3351 | 3352 | Object.defineProperty(target, component, { 3353 | 3354 | get: function() { 3355 | 3356 | if (this.__state.space === 'RGB') { 3357 | return this.__state[component]; 3358 | } 3359 | 3360 | recalculateRGB(this, component, componentHexIndex); 3361 | 3362 | return this.__state[component]; 3363 | 3364 | }, 3365 | 3366 | set: function(v) { 3367 | 3368 | if (this.__state.space !== 'RGB') { 3369 | recalculateRGB(this, component, componentHexIndex); 3370 | this.__state.space = 'RGB'; 3371 | } 3372 | 3373 | this.__state[component] = v; 3374 | 3375 | } 3376 | 3377 | }); 3378 | 3379 | } 3380 | 3381 | function defineHSVComponent(target, component) { 3382 | 3383 | Object.defineProperty(target, component, { 3384 | 3385 | get: function() { 3386 | 3387 | if (this.__state.space === 'HSV') 3388 | return this.__state[component]; 3389 | 3390 | recalculateHSV(this); 3391 | 3392 | return this.__state[component]; 3393 | 3394 | }, 3395 | 3396 | set: function(v) { 3397 | 3398 | if (this.__state.space !== 'HSV') { 3399 | recalculateHSV(this); 3400 | this.__state.space = 'HSV'; 3401 | } 3402 | 3403 | this.__state[component] = v; 3404 | 3405 | } 3406 | 3407 | }); 3408 | 3409 | } 3410 | 3411 | function recalculateRGB(color, component, componentHexIndex) { 3412 | 3413 | if (color.__state.space === 'HEX') { 3414 | 3415 | color.__state[component] = math.component_from_hex(color.__state.hex, componentHexIndex); 3416 | 3417 | } else if (color.__state.space === 'HSV') { 3418 | 3419 | common.extend(color.__state, math.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); 3420 | 3421 | } else { 3422 | 3423 | throw 'Corrupted color state'; 3424 | 3425 | } 3426 | 3427 | } 3428 | 3429 | function recalculateHSV(color) { 3430 | 3431 | var result = math.rgb_to_hsv(color.r, color.g, color.b); 3432 | 3433 | common.extend(color.__state, 3434 | { 3435 | s: result.s, 3436 | v: result.v 3437 | } 3438 | ); 3439 | 3440 | if (!common.isNaN(result.h)) { 3441 | color.__state.h = result.h; 3442 | } else if (common.isUndefined(color.__state.h)) { 3443 | color.__state.h = 0; 3444 | } 3445 | 3446 | } 3447 | 3448 | return Color; 3449 | 3450 | })(dat.color.interpret, 3451 | dat.color.math = (function () { 3452 | 3453 | var tmpComponent; 3454 | 3455 | return { 3456 | 3457 | hsv_to_rgb: function(h, s, v) { 3458 | 3459 | var hi = Math.floor(h / 60) % 6; 3460 | 3461 | var f = h / 60 - Math.floor(h / 60); 3462 | var p = v * (1.0 - s); 3463 | var q = v * (1.0 - (f * s)); 3464 | var t = v * (1.0 - ((1.0 - f) * s)); 3465 | var c = [ 3466 | [v, t, p], 3467 | [q, v, p], 3468 | [p, v, t], 3469 | [p, q, v], 3470 | [t, p, v], 3471 | [v, p, q] 3472 | ][hi]; 3473 | 3474 | return { 3475 | r: c[0] * 255, 3476 | g: c[1] * 255, 3477 | b: c[2] * 255 3478 | }; 3479 | 3480 | }, 3481 | 3482 | rgb_to_hsv: function(r, g, b) { 3483 | 3484 | var min = Math.min(r, g, b), 3485 | max = Math.max(r, g, b), 3486 | delta = max - min, 3487 | h, s; 3488 | 3489 | if (max != 0) { 3490 | s = delta / max; 3491 | } else { 3492 | return { 3493 | h: NaN, 3494 | s: 0, 3495 | v: 0 3496 | }; 3497 | } 3498 | 3499 | if (r == max) { 3500 | h = (g - b) / delta; 3501 | } else if (g == max) { 3502 | h = 2 + (b - r) / delta; 3503 | } else { 3504 | h = 4 + (r - g) / delta; 3505 | } 3506 | h /= 6; 3507 | if (h < 0) { 3508 | h += 1; 3509 | } 3510 | 3511 | return { 3512 | h: h * 360, 3513 | s: s, 3514 | v: max / 255 3515 | }; 3516 | }, 3517 | 3518 | rgb_to_hex: function(r, g, b) { 3519 | var hex = this.hex_with_component(0, 2, r); 3520 | hex = this.hex_with_component(hex, 1, g); 3521 | hex = this.hex_with_component(hex, 0, b); 3522 | return hex; 3523 | }, 3524 | 3525 | component_from_hex: function(hex, componentIndex) { 3526 | return (hex >> (componentIndex * 8)) & 0xFF; 3527 | }, 3528 | 3529 | hex_with_component: function(hex, componentIndex, value) { 3530 | return value << (tmpComponent = componentIndex * 8) | (hex & ~ (0xFF << tmpComponent)); 3531 | } 3532 | 3533 | } 3534 | 3535 | })(), 3536 | dat.color.toString, 3537 | dat.utils.common), 3538 | dat.color.interpret, 3539 | dat.utils.common), 3540 | dat.utils.requestAnimationFrame = (function () { 3541 | 3542 | /** 3543 | * requirejs version of Paul Irish's RequestAnimationFrame 3544 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 3545 | */ 3546 | 3547 | return window.webkitRequestAnimationFrame || 3548 | window.mozRequestAnimationFrame || 3549 | window.oRequestAnimationFrame || 3550 | window.msRequestAnimationFrame || 3551 | function(callback, element) { 3552 | 3553 | window.setTimeout(callback, 1000 / 60); 3554 | 3555 | }; 3556 | })(), 3557 | dat.dom.CenteredDiv = (function (dom, common) { 3558 | 3559 | 3560 | var CenteredDiv = function() { 3561 | 3562 | this.backgroundElement = document.createElement('div'); 3563 | common.extend(this.backgroundElement.style, { 3564 | backgroundColor: 'rgba(0,0,0,0.8)', 3565 | top: 0, 3566 | left: 0, 3567 | display: 'none', 3568 | zIndex: '1000', 3569 | opacity: 0, 3570 | WebkitTransition: 'opacity 0.2s linear' 3571 | }); 3572 | 3573 | dom.makeFullscreen(this.backgroundElement); 3574 | this.backgroundElement.style.position = 'fixed'; 3575 | 3576 | this.domElement = document.createElement('div'); 3577 | common.extend(this.domElement.style, { 3578 | position: 'fixed', 3579 | display: 'none', 3580 | zIndex: '1001', 3581 | opacity: 0, 3582 | WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear' 3583 | }); 3584 | 3585 | 3586 | document.body.appendChild(this.backgroundElement); 3587 | document.body.appendChild(this.domElement); 3588 | 3589 | var _this = this; 3590 | dom.bind(this.backgroundElement, 'click', function() { 3591 | _this.hide(); 3592 | }); 3593 | 3594 | 3595 | }; 3596 | 3597 | CenteredDiv.prototype.show = function() { 3598 | 3599 | var _this = this; 3600 | 3601 | 3602 | 3603 | this.backgroundElement.style.display = 'block'; 3604 | 3605 | this.domElement.style.display = 'block'; 3606 | this.domElement.style.opacity = 0; 3607 | // this.domElement.style.top = '52%'; 3608 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3609 | 3610 | this.layout(); 3611 | 3612 | common.defer(function() { 3613 | _this.backgroundElement.style.opacity = 1; 3614 | _this.domElement.style.opacity = 1; 3615 | _this.domElement.style.webkitTransform = 'scale(1)'; 3616 | }); 3617 | 3618 | }; 3619 | 3620 | CenteredDiv.prototype.hide = function() { 3621 | 3622 | var _this = this; 3623 | 3624 | var hide = function() { 3625 | 3626 | _this.domElement.style.display = 'none'; 3627 | _this.backgroundElement.style.display = 'none'; 3628 | 3629 | dom.unbind(_this.domElement, 'webkitTransitionEnd', hide); 3630 | dom.unbind(_this.domElement, 'transitionend', hide); 3631 | dom.unbind(_this.domElement, 'oTransitionEnd', hide); 3632 | 3633 | }; 3634 | 3635 | dom.bind(this.domElement, 'webkitTransitionEnd', hide); 3636 | dom.bind(this.domElement, 'transitionend', hide); 3637 | dom.bind(this.domElement, 'oTransitionEnd', hide); 3638 | 3639 | this.backgroundElement.style.opacity = 0; 3640 | // this.domElement.style.top = '48%'; 3641 | this.domElement.style.opacity = 0; 3642 | this.domElement.style.webkitTransform = 'scale(1.1)'; 3643 | 3644 | }; 3645 | 3646 | CenteredDiv.prototype.layout = function() { 3647 | this.domElement.style.left = window.innerWidth/2 - dom.getWidth(this.domElement) / 2 + 'px'; 3648 | this.domElement.style.top = window.innerHeight/2 - dom.getHeight(this.domElement) / 2 + 'px'; 3649 | }; 3650 | 3651 | function lockScroll(e) { 3652 | console.log(e); 3653 | } 3654 | 3655 | return CenteredDiv; 3656 | 3657 | })(dat.dom.dom, 3658 | dat.utils.common), 3659 | dat.dom.dom, 3660 | dat.utils.common); --------------------------------------------------------------------------------