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