├── README.md
├── assets
├── css
│ └── common.css
└── js
│ ├── lib
│ ├── TrackballControls.js
│ ├── TweenMax.js
│ ├── dat.gui.js
│ ├── jquery.js
│ ├── three.js
│ └── webfontloader.js
│ ├── step1
│ ├── MainVisual.js
│ └── index.js
│ ├── step2
│ ├── FloatingChars.js
│ ├── FloatingCharsGeometry.js
│ ├── MainVisual.js
│ └── index.js
│ ├── step3
│ ├── FloatingChars.js
│ ├── FloatingCharsGeometry.js
│ ├── MainVisual.js
│ └── index.js
│ ├── step4
│ ├── FloatingChars.js
│ ├── FloatingCharsGeometry.js
│ ├── MainVisual.js
│ └── index.js
│ └── step5
│ ├── FloatingChars.js
│ ├── FloatingCharsGeometry.js
│ ├── MainVisual.js
│ └── index.js
├── step1
└── index.html
├── step2
└── index.html
├── step3
└── index.html
├── step4
└── index.html
└── step5
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | # 2016.12.03 GLSL Workshop
2 | 2016.12.03 GLSL Workshopの資料です。
3 |
4 | https://tkmh.me/study/glslWorkshop20161203/slide/
5 |
6 | ## サンプル
7 | https://tkmh.me/study/glslWorkshop20161203/sample/step1/
8 | https://tkmh.me/study/glslWorkshop20161203/sample/step2/
9 | https://tkmh.me/study/glslWorkshop20161203/sample/step3/
10 | https://tkmh.me/study/glslWorkshop20161203/sample/step4/
11 | https://tkmh.me/study/glslWorkshop20161203/sample/step5/
12 |
13 | ## three.jsの基礎
14 | https://html5experts.jp/yomotsu/5225/
15 | http://qiita.com/nogson/items/e5e4a5a09f7d594eabf8
16 |
17 | ## UV座標
18 | https://wgld.org/d/webgl/w026.html
19 |
20 | ## three.js document
21 | https://threejs.org/docs/
22 |
23 | ### PlaneGeometry
24 | https://threejs.org/docs/#Reference/Geometries/PlaneGeometry
25 |
26 | ### BoxGeometry
27 | https://threejs.org/docs/#Reference/Geometries/BoxGeometry
28 |
29 | ### BufferGeometry
30 | https://threejs.org/docs/#Reference/Core/BufferGeometry
31 |
32 | ### RawShaderMaterial
33 | https://threejs.org/docs/#Reference/Materials/RawShaderMaterial
34 |
--------------------------------------------------------------------------------
/assets/css/common.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | }
5 |
6 | #main {
7 | position: absolute;
8 | top: 0;
9 | left: 0;
10 | width: 100%;
11 | height: 100%;
12 | background-color: #fff;
13 | }
14 |
15 | #main canvas {
16 | position: absolute;
17 | }
18 |
--------------------------------------------------------------------------------
/assets/js/lib/TrackballControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Eberhard Graether / http://egraether.com/
3 | * @author Mark Lundin / http://mark-lundin.com
4 | * @author Simone Manini / http://daron1337.github.io
5 | * @author Luca Antiga / http://lantiga.github.io
6 | */
7 |
8 | THREE.TrackballControls = function ( object, domElement ) {
9 |
10 | var _this = this;
11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
12 |
13 | this.object = object;
14 | this.domElement = ( domElement !== undefined ) ? domElement : document;
15 |
16 | // API
17 |
18 | this.enabled = true;
19 |
20 | this.screen = { left: 0, top: 0, width: 0, height: 0 };
21 |
22 | this.rotateSpeed = 1.0;
23 | this.zoomSpeed = 1.2;
24 | this.panSpeed = 0.3;
25 |
26 | this.noRotate = false;
27 | this.noZoom = false;
28 | this.noPan = false;
29 |
30 | this.staticMoving = false;
31 | this.dynamicDampingFactor = 0.2;
32 |
33 | this.minDistance = 0;
34 | this.maxDistance = Infinity;
35 |
36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
37 |
38 | // internals
39 |
40 | this.target = new THREE.Vector3();
41 |
42 | var EPS = 0.000001;
43 |
44 | var lastPosition = new THREE.Vector3();
45 |
46 | var _state = STATE.NONE,
47 | _prevState = STATE.NONE,
48 |
49 | _eye = new THREE.Vector3(),
50 |
51 | _movePrev = new THREE.Vector2(),
52 | _moveCurr = new THREE.Vector2(),
53 |
54 | _lastAxis = new THREE.Vector3(),
55 | _lastAngle = 0,
56 |
57 | _zoomStart = new THREE.Vector2(),
58 | _zoomEnd = new THREE.Vector2(),
59 |
60 | _touchZoomDistanceStart = 0,
61 | _touchZoomDistanceEnd = 0,
62 |
63 | _panStart = new THREE.Vector2(),
64 | _panEnd = new THREE.Vector2();
65 |
66 | // for reset
67 |
68 | this.target0 = this.target.clone();
69 | this.position0 = this.object.position.clone();
70 | this.up0 = this.object.up.clone();
71 |
72 | // events
73 |
74 | var changeEvent = { type: 'change' };
75 | var startEvent = { type: 'start' };
76 | var endEvent = { type: 'end' };
77 |
78 |
79 | // methods
80 |
81 | this.handleResize = function () {
82 |
83 | if ( this.domElement === document ) {
84 |
85 | this.screen.left = 0;
86 | this.screen.top = 0;
87 | this.screen.width = window.innerWidth;
88 | this.screen.height = window.innerHeight;
89 |
90 | } else {
91 |
92 | var box = this.domElement.getBoundingClientRect();
93 | // adjustments come from similar code in the jquery offset() function
94 | var d = this.domElement.ownerDocument.documentElement;
95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft;
96 | this.screen.top = box.top + window.pageYOffset - d.clientTop;
97 | this.screen.width = box.width;
98 | this.screen.height = box.height;
99 |
100 | }
101 |
102 | };
103 |
104 | this.handleEvent = function ( event ) {
105 |
106 | if ( typeof this[ event.type ] == 'function' ) {
107 |
108 | this[ event.type ]( event );
109 |
110 | }
111 |
112 | };
113 |
114 | var getMouseOnScreen = ( function () {
115 |
116 | var vector = new THREE.Vector2();
117 |
118 | return function getMouseOnScreen( pageX, pageY ) {
119 |
120 | vector.set(
121 | ( pageX - _this.screen.left ) / _this.screen.width,
122 | ( pageY - _this.screen.top ) / _this.screen.height
123 | );
124 |
125 | return vector;
126 |
127 | };
128 |
129 | }() );
130 |
131 | var getMouseOnCircle = ( function () {
132 |
133 | var vector = new THREE.Vector2();
134 |
135 | return function getMouseOnCircle( pageX, pageY ) {
136 |
137 | vector.set(
138 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ),
139 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional
140 | );
141 |
142 | return vector;
143 |
144 | };
145 |
146 | }() );
147 |
148 | this.rotateCamera = ( function() {
149 |
150 | var axis = new THREE.Vector3(),
151 | quaternion = new THREE.Quaternion(),
152 | eyeDirection = new THREE.Vector3(),
153 | objectUpDirection = new THREE.Vector3(),
154 | objectSidewaysDirection = new THREE.Vector3(),
155 | moveDirection = new THREE.Vector3(),
156 | angle;
157 |
158 | return function rotateCamera() {
159 |
160 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
161 | angle = moveDirection.length();
162 |
163 | if ( angle ) {
164 |
165 | _eye.copy( _this.object.position ).sub( _this.target );
166 |
167 | eyeDirection.copy( _eye ).normalize();
168 | objectUpDirection.copy( _this.object.up ).normalize();
169 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
170 |
171 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
172 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
173 |
174 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
175 |
176 | axis.crossVectors( moveDirection, _eye ).normalize();
177 |
178 | angle *= _this.rotateSpeed;
179 | quaternion.setFromAxisAngle( axis, angle );
180 |
181 | _eye.applyQuaternion( quaternion );
182 | _this.object.up.applyQuaternion( quaternion );
183 |
184 | _lastAxis.copy( axis );
185 | _lastAngle = angle;
186 |
187 | } else if ( ! _this.staticMoving && _lastAngle ) {
188 |
189 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor );
190 | _eye.copy( _this.object.position ).sub( _this.target );
191 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
192 | _eye.applyQuaternion( quaternion );
193 | _this.object.up.applyQuaternion( quaternion );
194 |
195 | }
196 |
197 | _movePrev.copy( _moveCurr );
198 |
199 | };
200 |
201 | }() );
202 |
203 |
204 | this.zoomCamera = function () {
205 |
206 | var factor;
207 |
208 | if ( _state === STATE.TOUCH_ZOOM_PAN ) {
209 |
210 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
211 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
212 | _eye.multiplyScalar( factor );
213 |
214 | } else {
215 |
216 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
217 |
218 | if ( factor !== 1.0 && factor > 0.0 ) {
219 |
220 | _eye.multiplyScalar( factor );
221 |
222 | }
223 |
224 | if ( _this.staticMoving ) {
225 |
226 | _zoomStart.copy( _zoomEnd );
227 |
228 | } else {
229 |
230 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
231 |
232 | }
233 |
234 | }
235 |
236 | };
237 |
238 | this.panCamera = ( function() {
239 |
240 | var mouseChange = new THREE.Vector2(),
241 | objectUp = new THREE.Vector3(),
242 | pan = new THREE.Vector3();
243 |
244 | return function panCamera() {
245 |
246 | mouseChange.copy( _panEnd ).sub( _panStart );
247 |
248 | if ( mouseChange.lengthSq() ) {
249 |
250 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
251 |
252 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
253 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
254 |
255 | _this.object.position.add( pan );
256 | _this.target.add( pan );
257 |
258 | if ( _this.staticMoving ) {
259 |
260 | _panStart.copy( _panEnd );
261 |
262 | } else {
263 |
264 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
265 |
266 | }
267 |
268 | }
269 |
270 | };
271 |
272 | }() );
273 |
274 | this.checkDistances = function () {
275 |
276 | if ( ! _this.noZoom || ! _this.noPan ) {
277 |
278 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
279 |
280 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
281 | _zoomStart.copy( _zoomEnd );
282 |
283 | }
284 |
285 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
286 |
287 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
288 | _zoomStart.copy( _zoomEnd );
289 |
290 | }
291 |
292 | }
293 |
294 | };
295 |
296 | this.update = function () {
297 |
298 | _eye.subVectors( _this.object.position, _this.target );
299 |
300 | if ( ! _this.noRotate ) {
301 |
302 | _this.rotateCamera();
303 |
304 | }
305 |
306 | if ( ! _this.noZoom ) {
307 |
308 | _this.zoomCamera();
309 |
310 | }
311 |
312 | if ( ! _this.noPan ) {
313 |
314 | _this.panCamera();
315 |
316 | }
317 |
318 | _this.object.position.addVectors( _this.target, _eye );
319 |
320 | _this.checkDistances();
321 |
322 | _this.object.lookAt( _this.target );
323 |
324 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) {
325 |
326 | _this.dispatchEvent( changeEvent );
327 |
328 | lastPosition.copy( _this.object.position );
329 |
330 | }
331 |
332 | };
333 |
334 | this.reset = function () {
335 |
336 | _state = STATE.NONE;
337 | _prevState = STATE.NONE;
338 |
339 | _this.target.copy( _this.target0 );
340 | _this.object.position.copy( _this.position0 );
341 | _this.object.up.copy( _this.up0 );
342 |
343 | _eye.subVectors( _this.object.position, _this.target );
344 |
345 | _this.object.lookAt( _this.target );
346 |
347 | _this.dispatchEvent( changeEvent );
348 |
349 | lastPosition.copy( _this.object.position );
350 |
351 | };
352 |
353 | // listeners
354 |
355 | function keydown( event ) {
356 |
357 | if ( _this.enabled === false ) return;
358 |
359 | window.removeEventListener( 'keydown', keydown );
360 |
361 | _prevState = _state;
362 |
363 | if ( _state !== STATE.NONE ) {
364 |
365 | return;
366 |
367 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
368 |
369 | _state = STATE.ROTATE;
370 |
371 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
372 |
373 | _state = STATE.ZOOM;
374 |
375 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
376 |
377 | _state = STATE.PAN;
378 |
379 | }
380 |
381 | }
382 |
383 | function keyup( event ) {
384 |
385 | if ( _this.enabled === false ) return;
386 |
387 | _state = _prevState;
388 |
389 | window.addEventListener( 'keydown', keydown, false );
390 |
391 | }
392 |
393 | function mousedown( event ) {
394 |
395 | if ( _this.enabled === false ) return;
396 |
397 | event.preventDefault();
398 | event.stopPropagation();
399 |
400 | if ( _state === STATE.NONE ) {
401 |
402 | _state = event.button;
403 |
404 | }
405 |
406 | if ( _state === STATE.ROTATE && ! _this.noRotate ) {
407 |
408 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
409 | _movePrev.copy( _moveCurr );
410 |
411 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
412 |
413 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
414 | _zoomEnd.copy( _zoomStart );
415 |
416 | } else if ( _state === STATE.PAN && ! _this.noPan ) {
417 |
418 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
419 | _panEnd.copy( _panStart );
420 |
421 | }
422 |
423 | document.addEventListener( 'mousemove', mousemove, false );
424 | document.addEventListener( 'mouseup', mouseup, false );
425 |
426 | _this.dispatchEvent( startEvent );
427 |
428 | }
429 |
430 | function mousemove( event ) {
431 |
432 | if ( _this.enabled === false ) return;
433 |
434 | event.preventDefault();
435 | event.stopPropagation();
436 |
437 | if ( _state === STATE.ROTATE && ! _this.noRotate ) {
438 |
439 | _movePrev.copy( _moveCurr );
440 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
441 |
442 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
443 |
444 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
445 |
446 | } else if ( _state === STATE.PAN && ! _this.noPan ) {
447 |
448 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
449 |
450 | }
451 |
452 | }
453 |
454 | function mouseup( event ) {
455 |
456 | if ( _this.enabled === false ) return;
457 |
458 | event.preventDefault();
459 | event.stopPropagation();
460 |
461 | _state = STATE.NONE;
462 |
463 | document.removeEventListener( 'mousemove', mousemove );
464 | document.removeEventListener( 'mouseup', mouseup );
465 | _this.dispatchEvent( endEvent );
466 |
467 | }
468 |
469 | function mousewheel( event ) {
470 |
471 | if ( _this.enabled === false ) return;
472 |
473 | event.preventDefault();
474 | event.stopPropagation();
475 |
476 | _zoomStart.y -= event.deltaY * 0.01;
477 |
478 | _this.dispatchEvent( startEvent );
479 | _this.dispatchEvent( endEvent );
480 |
481 | }
482 |
483 | function touchstart( event ) {
484 |
485 | if ( _this.enabled === false ) return;
486 |
487 | switch ( event.touches.length ) {
488 |
489 | case 1:
490 | _state = STATE.TOUCH_ROTATE;
491 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
492 | _movePrev.copy( _moveCurr );
493 | break;
494 |
495 | default: // 2 or more
496 | _state = STATE.TOUCH_ZOOM_PAN;
497 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
498 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
499 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
500 |
501 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
502 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
503 | _panStart.copy( getMouseOnScreen( x, y ) );
504 | _panEnd.copy( _panStart );
505 | break;
506 |
507 | }
508 |
509 | _this.dispatchEvent( startEvent );
510 |
511 | }
512 |
513 | function touchmove( event ) {
514 |
515 | if ( _this.enabled === false ) return;
516 |
517 | event.preventDefault();
518 | event.stopPropagation();
519 |
520 | switch ( event.touches.length ) {
521 |
522 | case 1:
523 | _movePrev.copy( _moveCurr );
524 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
525 | break;
526 |
527 | default: // 2 or more
528 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
529 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
530 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
531 |
532 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
533 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
534 | _panEnd.copy( getMouseOnScreen( x, y ) );
535 | break;
536 |
537 | }
538 |
539 | }
540 |
541 | function touchend( event ) {
542 |
543 | if ( _this.enabled === false ) return;
544 |
545 | switch ( event.touches.length ) {
546 |
547 | case 0:
548 | _state = STATE.NONE;
549 | break;
550 |
551 | case 1:
552 | _state = STATE.TOUCH_ROTATE;
553 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
554 | _movePrev.copy( _moveCurr );
555 | break;
556 |
557 | }
558 |
559 | _this.dispatchEvent( endEvent );
560 |
561 | }
562 |
563 | function contextmenu( event ) {
564 |
565 | event.preventDefault();
566 |
567 | }
568 |
569 | this.dispose = function() {
570 |
571 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
572 | this.domElement.removeEventListener( 'mousedown', mousedown, false );
573 | this.domElement.removeEventListener( 'wheel', mousewheel, false );
574 |
575 | this.domElement.removeEventListener( 'touchstart', touchstart, false );
576 | this.domElement.removeEventListener( 'touchend', touchend, false );
577 | this.domElement.removeEventListener( 'touchmove', touchmove, false );
578 |
579 | document.removeEventListener( 'mousemove', mousemove, false );
580 | document.removeEventListener( 'mouseup', mouseup, false );
581 |
582 | window.removeEventListener( 'keydown', keydown, false );
583 | window.removeEventListener( 'keyup', keyup, false );
584 |
585 | };
586 |
587 | this.domElement.addEventListener( 'contextmenu', contextmenu, false );
588 | this.domElement.addEventListener( 'mousedown', mousedown, false );
589 | this.domElement.addEventListener( 'wheel', mousewheel, false );
590 |
591 | this.domElement.addEventListener( 'touchstart', touchstart, false );
592 | this.domElement.addEventListener( 'touchend', touchend, false );
593 | this.domElement.addEventListener( 'touchmove', touchmove, false );
594 |
595 | window.addEventListener( 'keydown', keydown, false );
596 | window.addEventListener( 'keyup', keyup, false );
597 |
598 | this.handleResize();
599 |
600 | // force an update at start
601 | this.update();
602 |
603 | };
604 |
605 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
606 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
607 |
--------------------------------------------------------------------------------
/assets/js/lib/dat.gui.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 |
110 | if (ARR_EACH && obj.forEach === ARR_EACH) {
111 |
112 | obj.forEach(itr, scope);
113 |
114 | } else if (obj.length === obj.length + 0) { // Is number but not NaN
115 |
116 | for (var key = 0, l = obj.length; key < l; key++)
117 | if (key in obj && itr.call(scope, obj[key], key) === this.BREAK)
118 | return;
119 |
120 | } else {
121 |
122 | for (var key in obj)
123 | if (itr.call(scope, obj[key], key) === this.BREAK)
124 | return;
125 |
126 | }
127 |
128 | },
129 |
130 | defer: function(fnc) {
131 | setTimeout(fnc, 0);
132 | },
133 |
134 | toArray: function(obj) {
135 | if (obj.toArray) return obj.toArray();
136 | return ARR_SLICE.call(obj);
137 | },
138 |
139 | isUndefined: function(obj) {
140 | return obj === undefined;
141 | },
142 |
143 | isNull: function(obj) {
144 | return obj === null;
145 | },
146 |
147 | isNaN: function(obj) {
148 | return obj !== obj;
149 | },
150 |
151 | isArray: Array.isArray || function(obj) {
152 | return obj.constructor === Array;
153 | },
154 |
155 | isObject: function(obj) {
156 | return obj === Object(obj);
157 | },
158 |
159 | isNumber: function(obj) {
160 | return obj === obj+0;
161 | },
162 |
163 | isString: function(obj) {
164 | return obj === obj+'';
165 | },
166 |
167 | isBoolean: function(obj) {
168 | return obj === false || obj === true;
169 | },
170 |
171 | isFunction: function(obj) {
172 | return Object.prototype.toString.call(obj) === '[object Function]';
173 | }
174 |
175 | };
176 |
177 | })();
178 |
179 |
180 | dat.controllers.Controller = (function (common) {
181 |
182 | /**
183 | * @class An "abstract" class that represents a given property of an object.
184 | *
185 | * @param {Object} object The object to be manipulated
186 | * @param {string} property The name of the property to be manipulated
187 | *
188 | * @member dat.controllers
189 | */
190 | var Controller = function(object, property) {
191 |
192 | this.initialValue = object[property];
193 |
194 | /**
195 | * Those who extend this class will put their DOM elements in here.
196 | * @type {DOMElement}
197 | */
198 | this.domElement = document.createElement('div');
199 |
200 | /**
201 | * The object to manipulate
202 | * @type {Object}
203 | */
204 | this.object = object;
205 |
206 | /**
207 | * The name of the property to manipulate
208 | * @type {String}
209 | */
210 | this.property = property;
211 |
212 | /**
213 | * The function to be called on change.
214 | * @type {Function}
215 | * @ignore
216 | */
217 | this.__onChange = undefined;
218 |
219 | /**
220 | * The function to be called on finishing change.
221 | * @type {Function}
222 | * @ignore
223 | */
224 | this.__onFinishChange = undefined;
225 |
226 | };
227 |
228 | common.extend(
229 |
230 | Controller.prototype,
231 |
232 | /** @lends dat.controllers.Controller.prototype */
233 | {
234 |
235 | /**
236 | * Specify that a function fire every time someone changes the value with
237 | * this Controller.
238 | *
239 | * @param {Function} fnc This function will be called whenever the value
240 | * is modified via this Controller.
241 | * @returns {dat.controllers.Controller} this
242 | */
243 | onChange: function(fnc) {
244 | this.__onChange = fnc;
245 | return this;
246 | },
247 |
248 | /**
249 | * Specify that a function fire every time someone "finishes" changing
250 | * the value wih this Controller. Useful for values that change
251 | * incrementally like numbers or strings.
252 | *
253 | * @param {Function} fnc This function will be called whenever
254 | * someone "finishes" changing the value via this Controller.
255 | * @returns {dat.controllers.Controller} this
256 | */
257 | onFinishChange: function(fnc) {
258 | this.__onFinishChange = fnc;
259 | return this;
260 | },
261 |
262 | /**
263 | * Change the value of object[property]
264 | *
265 | * @param {Object} newValue The new value of object[property]
266 | */
267 | setValue: function(newValue) {
268 | this.object[this.property] = newValue;
269 | if (this.__onChange) {
270 | this.__onChange.call(this, newValue);
271 | }
272 | this.updateDisplay();
273 | return this;
274 | },
275 |
276 | /**
277 | * Gets the value of object[property]
278 | *
279 | * @returns {Object} The current value of object[property]
280 | */
281 | getValue: function() {
282 | return this.object[this.property];
283 | },
284 |
285 | /**
286 | * Refreshes the visual display of a Controller in order to keep sync
287 | * with the object's current value.
288 | * @returns {dat.controllers.Controller} this
289 | */
290 | updateDisplay: function() {
291 | return this;
292 | },
293 |
294 | /**
295 | * @returns {Boolean} true if the value has deviated from initialValue
296 | */
297 | isModified: function() {
298 | return this.initialValue !== this.getValue()
299 | }
300 |
301 | }
302 |
303 | );
304 |
305 | return Controller;
306 |
307 |
308 | })(dat.utils.common);
309 |
310 |
311 | dat.dom.dom = (function (common) {
312 |
313 | var EVENT_MAP = {
314 | 'HTMLEvents': ['change'],
315 | 'MouseEvents': ['click','mousemove','mousedown','mouseup', 'mouseover'],
316 | 'KeyboardEvents': ['keydown']
317 | };
318 |
319 | var EVENT_MAP_INV = {};
320 | common.each(EVENT_MAP, function(v, k) {
321 | common.each(v, function(e) {
322 | EVENT_MAP_INV[e] = k;
323 | });
324 | });
325 |
326 | var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/;
327 |
328 | function cssValueToPixels(val) {
329 |
330 | if (val === '0' || common.isUndefined(val)) return 0;
331 |
332 | var match = val.match(CSS_VALUE_PIXELS);
333 |
334 | if (!common.isNull(match)) {
335 | return parseFloat(match[1]);
336 | }
337 |
338 | // TODO ...ems? %?
339 |
340 | return 0;
341 |
342 | }
343 |
344 | /**
345 | * @namespace
346 | * @member dat.dom
347 | */
348 | var dom = {
349 |
350 | /**
351 | *
352 | * @param elem
353 | * @param selectable
354 | */
355 | makeSelectable: function(elem, selectable) {
356 |
357 | if (elem === undefined || elem.style === undefined) return;
358 |
359 | elem.onselectstart = selectable ? function() {
360 | return false;
361 | } : function() {
362 | };
363 |
364 | elem.style.MozUserSelect = selectable ? 'auto' : 'none';
365 | elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none';
366 | elem.unselectable = selectable ? 'on' : 'off';
367 |
368 | },
369 |
370 | /**
371 | *
372 | * @param elem
373 | * @param horizontal
374 | * @param vertical
375 | */
376 | makeFullscreen: function(elem, horizontal, vertical) {
377 |
378 | if (common.isUndefined(horizontal)) horizontal = true;
379 | if (common.isUndefined(vertical)) vertical = true;
380 |
381 | elem.style.position = 'absolute';
382 |
383 | if (horizontal) {
384 | elem.style.left = 0;
385 | elem.style.right = 0;
386 | }
387 | if (vertical) {
388 | elem.style.top = 0;
389 | elem.style.bottom = 0;
390 | }
391 |
392 | },
393 |
394 | /**
395 | *
396 | * @param elem
397 | * @param eventType
398 | * @param params
399 | */
400 | fakeEvent: function(elem, eventType, params, aux) {
401 | params = params || {};
402 | var className = EVENT_MAP_INV[eventType];
403 | if (!className) {
404 | throw new Error('Event type ' + eventType + ' not supported.');
405 | }
406 | var evt = document.createEvent(className);
407 | switch (className) {
408 | case 'MouseEvents':
409 | var clientX = params.x || params.clientX || 0;
410 | var clientY = params.y || params.clientY || 0;
411 | evt.initMouseEvent(eventType, params.bubbles || false,
412 | params.cancelable || true, window, params.clickCount || 1,
413 | 0, //screen X
414 | 0, //screen Y
415 | clientX, //client X
416 | clientY, //client Y
417 | false, false, false, false, 0, null);
418 | break;
419 | case 'KeyboardEvents':
420 | var init = evt.initKeyboardEvent || evt.initKeyEvent; // webkit || moz
421 | common.defaults(params, {
422 | cancelable: true,
423 | ctrlKey: false,
424 | altKey: false,
425 | shiftKey: false,
426 | metaKey: false,
427 | keyCode: undefined,
428 | charCode: undefined
429 | });
430 | init(eventType, params.bubbles || false,
431 | params.cancelable, window,
432 | params.ctrlKey, params.altKey,
433 | params.shiftKey, params.metaKey,
434 | params.keyCode, params.charCode);
435 | break;
436 | default:
437 | evt.initEvent(eventType, params.bubbles || false,
438 | params.cancelable || true);
439 | break;
440 | }
441 | common.defaults(evt, aux);
442 | elem.dispatchEvent(evt);
443 | },
444 |
445 | /**
446 | *
447 | * @param elem
448 | * @param event
449 | * @param func
450 | * @param bool
451 | */
452 | bind: function(elem, event, func, bool) {
453 | bool = bool || false;
454 | if (elem.addEventListener)
455 | elem.addEventListener(event, func, bool);
456 | else if (elem.attachEvent)
457 | elem.attachEvent('on' + event, func);
458 | return dom;
459 | },
460 |
461 | /**
462 | *
463 | * @param elem
464 | * @param event
465 | * @param func
466 | * @param bool
467 | */
468 | unbind: function(elem, event, func, bool) {
469 | bool = bool || false;
470 | if (elem.removeEventListener)
471 | elem.removeEventListener(event, func, bool);
472 | else if (elem.detachEvent)
473 | elem.detachEvent('on' + event, func);
474 | return dom;
475 | },
476 |
477 | /**
478 | *
479 | * @param elem
480 | * @param className
481 | */
482 | addClass: function(elem, className) {
483 | if (elem.className === undefined) {
484 | elem.className = className;
485 | } else if (elem.className !== className) {
486 | var classes = elem.className.split(/ +/);
487 | if (classes.indexOf(className) == -1) {
488 | classes.push(className);
489 | elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, '');
490 | }
491 | }
492 | return dom;
493 | },
494 |
495 | /**
496 | *
497 | * @param elem
498 | * @param className
499 | */
500 | removeClass: function(elem, className) {
501 | if (className) {
502 | if (elem.className === undefined) {
503 | // elem.className = className;
504 | } else if (elem.className === className) {
505 | elem.removeAttribute('class');
506 | } else {
507 | var classes = elem.className.split(/ +/);
508 | var index = classes.indexOf(className);
509 | if (index != -1) {
510 | classes.splice(index, 1);
511 | elem.className = classes.join(' ');
512 | }
513 | }
514 | } else {
515 | elem.className = undefined;
516 | }
517 | return dom;
518 | },
519 |
520 | hasClass: function(elem, className) {
521 | return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false;
522 | },
523 |
524 | /**
525 | *
526 | * @param elem
527 | */
528 | getWidth: function(elem) {
529 |
530 | var style = getComputedStyle(elem);
531 |
532 | return cssValueToPixels(style['border-left-width']) +
533 | cssValueToPixels(style['border-right-width']) +
534 | cssValueToPixels(style['padding-left']) +
535 | cssValueToPixels(style['padding-right']) +
536 | cssValueToPixels(style['width']);
537 | },
538 |
539 | /**
540 | *
541 | * @param elem
542 | */
543 | getHeight: function(elem) {
544 |
545 | var style = getComputedStyle(elem);
546 |
547 | return cssValueToPixels(style['border-top-width']) +
548 | cssValueToPixels(style['border-bottom-width']) +
549 | cssValueToPixels(style['padding-top']) +
550 | cssValueToPixels(style['padding-bottom']) +
551 | cssValueToPixels(style['height']);
552 | },
553 |
554 | /**
555 | *
556 | * @param elem
557 | */
558 | getOffset: function(elem) {
559 | var offset = {left: 0, top:0};
560 | if (elem.offsetParent) {
561 | do {
562 | offset.left += elem.offsetLeft;
563 | offset.top += elem.offsetTop;
564 | } while (elem = elem.offsetParent);
565 | }
566 | return offset;
567 | },
568 |
569 | // http://stackoverflow.com/posts/2684561/revisions
570 | /**
571 | *
572 | * @param elem
573 | */
574 | isActive: function(elem) {
575 | return elem === document.activeElement && ( elem.type || elem.href );
576 | }
577 |
578 | };
579 |
580 | return dom;
581 |
582 | })(dat.utils.common);
583 |
584 |
585 | dat.controllers.OptionController = (function (Controller, dom, common) {
586 |
587 | /**
588 | * @class Provides a select input to alter the property of an object, using a
589 | * list of accepted values.
590 | *
591 | * @extends dat.controllers.Controller
592 | *
593 | * @param {Object} object The object to be manipulated
594 | * @param {string} property The name of the property to be manipulated
595 | * @param {Object|string[]} options A map of labels to acceptable values, or
596 | * a list of acceptable string values.
597 | *
598 | * @member dat.controllers
599 | */
600 | var OptionController = function(object, property, options) {
601 |
602 | OptionController.superclass.call(this, object, property);
603 |
604 | var _this = this;
605 |
606 | /**
607 | * The drop down menu
608 | * @ignore
609 | */
610 | this.__select = document.createElement('select');
611 |
612 | if (common.isArray(options)) {
613 | var map = {};
614 | common.each(options, function(element) {
615 | map[element] = element;
616 | });
617 | options = map;
618 | }
619 |
620 | common.each(options, function(value, key) {
621 |
622 | var opt = document.createElement('option');
623 | opt.innerHTML = key;
624 | opt.setAttribute('value', value);
625 | _this.__select.appendChild(opt);
626 |
627 | });
628 |
629 | // Acknowledge original value
630 | this.updateDisplay();
631 |
632 | dom.bind(this.__select, 'change', function() {
633 | var desiredValue = this.options[this.selectedIndex].value;
634 | _this.setValue(desiredValue);
635 | });
636 |
637 | this.domElement.appendChild(this.__select);
638 |
639 | };
640 |
641 | OptionController.superclass = Controller;
642 |
643 | common.extend(
644 |
645 | OptionController.prototype,
646 | Controller.prototype,
647 |
648 | {
649 |
650 | setValue: function(v) {
651 | var toReturn = OptionController.superclass.prototype.setValue.call(this, v);
652 | if (this.__onFinishChange) {
653 | this.__onFinishChange.call(this, this.getValue());
654 | }
655 | return toReturn;
656 | },
657 |
658 | updateDisplay: function() {
659 | this.__select.value = this.getValue();
660 | return OptionController.superclass.prototype.updateDisplay.call(this);
661 | }
662 |
663 | }
664 |
665 | );
666 |
667 | return OptionController;
668 |
669 | })(dat.controllers.Controller,
670 | dat.dom.dom,
671 | dat.utils.common);
672 |
673 |
674 | dat.controllers.NumberController = (function (Controller, common) {
675 |
676 | /**
677 | * @class Represents a given property of an object that is a number.
678 | *
679 | * @extends dat.controllers.Controller
680 | *
681 | * @param {Object} object The object to be manipulated
682 | * @param {string} property The name of the property to be manipulated
683 | * @param {Object} [params] Optional parameters
684 | * @param {Number} [params.min] Minimum allowed value
685 | * @param {Number} [params.max] Maximum allowed value
686 | * @param {Number} [params.step] Increment by which to change value
687 | *
688 | * @member dat.controllers
689 | */
690 | var NumberController = function(object, property, params) {
691 |
692 | NumberController.superclass.call(this, object, property);
693 |
694 | params = params || {};
695 |
696 | this.__min = params.min;
697 | this.__max = params.max;
698 | this.__step = params.step;
699 |
700 | if (common.isUndefined(this.__step)) {
701 |
702 | if (this.initialValue == 0) {
703 | this.__impliedStep = 1; // What are we, psychics?
704 | } else {
705 | // Hey Doug, check this out.
706 | this.__impliedStep = Math.pow(10, Math.floor(Math.log(this.initialValue)/Math.LN10))/10;
707 | }
708 |
709 | } else {
710 |
711 | this.__impliedStep = this.__step;
712 |
713 | }
714 |
715 | this.__precision = numDecimals(this.__impliedStep);
716 |
717 |
718 | };
719 |
720 | NumberController.superclass = Controller;
721 |
722 | common.extend(
723 |
724 | NumberController.prototype,
725 | Controller.prototype,
726 |
727 | /** @lends dat.controllers.NumberController.prototype */
728 | {
729 |
730 | setValue: function(v) {
731 |
732 | if (this.__min !== undefined && v < this.__min) {
733 | v = this.__min;
734 | } else if (this.__max !== undefined && v > this.__max) {
735 | v = this.__max;
736 | }
737 |
738 | if (this.__step !== undefined && v % this.__step != 0) {
739 | v = Math.round(v / this.__step) * this.__step;
740 | }
741 |
742 | return NumberController.superclass.prototype.setValue.call(this, v);
743 |
744 | },
745 |
746 | /**
747 | * Specify a minimum value for object[property]
.
748 | *
749 | * @param {Number} minValue The minimum value for
750 | * object[property]
751 | * @returns {dat.controllers.NumberController} this
752 | */
753 | min: function(v) {
754 | this.__min = v;
755 | return this;
756 | },
757 |
758 | /**
759 | * Specify a maximum value for object[property]
.
760 | *
761 | * @param {Number} maxValue The maximum value for
762 | * object[property]
763 | * @returns {dat.controllers.NumberController} this
764 | */
765 | max: function(v) {
766 | this.__max = v;
767 | return this;
768 | },
769 |
770 | /**
771 | * Specify a step value that dat.controllers.NumberController
772 | * increments by.
773 | *
774 | * @param {Number} stepValue The step value for
775 | * dat.controllers.NumberController
776 | * @default if minimum and maximum specified increment is 1% of the
777 | * difference otherwise stepValue is 1
778 | * @returns {dat.controllers.NumberController} this
779 | */
780 | step: function(v) {
781 | this.__step = v;
782 | return this;
783 | }
784 |
785 | }
786 |
787 | );
788 |
789 | function numDecimals(x) {
790 | x = x.toString();
791 | if (x.indexOf('.') > -1) {
792 | return x.length - x.indexOf('.') - 1;
793 | } else {
794 | return 0;
795 | }
796 | }
797 |
798 | return NumberController;
799 |
800 | })(dat.controllers.Controller,
801 | dat.utils.common);
802 |
803 |
804 | dat.controllers.NumberControllerBox = (function (NumberController, dom, common) {
805 |
806 | /**
807 | * @class Represents a given property of an object that is a number and
808 | * provides an input element with which to manipulate it.
809 | *
810 | * @extends dat.controllers.Controller
811 | * @extends dat.controllers.NumberController
812 | *
813 | * @param {Object} object The object to be manipulated
814 | * @param {string} property The name of the property to be manipulated
815 | * @param {Object} [params] Optional parameters
816 | * @param {Number} [params.min] Minimum allowed value
817 | * @param {Number} [params.max] Maximum allowed value
818 | * @param {Number} [params.step] Increment by which to change value
819 | *
820 | * @member dat.controllers
821 | */
822 | var NumberControllerBox = function(object, property, params) {
823 |
824 | this.__truncationSuspended = false;
825 |
826 | NumberControllerBox.superclass.call(this, object, property, params);
827 |
828 | var _this = this;
829 |
830 | /**
831 | * {Number} Previous mouse y position
832 | * @ignore
833 | */
834 | var prev_y;
835 |
836 | this.__input = document.createElement('input');
837 | this.__input.setAttribute('type', 'text');
838 |
839 | // Makes it so manually specified values are not truncated.
840 |
841 | dom.bind(this.__input, 'change', onChange);
842 | dom.bind(this.__input, 'blur', onBlur);
843 | dom.bind(this.__input, 'mousedown', onMouseDown);
844 | dom.bind(this.__input, 'keydown', function(e) {
845 |
846 | // When pressing entire, you can be as precise as you want.
847 | if (e.keyCode === 13) {
848 | _this.__truncationSuspended = true;
849 | this.blur();
850 | _this.__truncationSuspended = false;
851 | }
852 |
853 | });
854 |
855 | function onChange() {
856 | var attempted = parseFloat(_this.__input.value);
857 | if (!common.isNaN(attempted)) _this.setValue(attempted);
858 | }
859 |
860 | function onBlur() {
861 | onChange();
862 | if (_this.__onFinishChange) {
863 | _this.__onFinishChange.call(_this, _this.getValue());
864 | }
865 | }
866 |
867 | function onMouseDown(e) {
868 | dom.bind(window, 'mousemove', onMouseDrag);
869 | dom.bind(window, 'mouseup', onMouseUp);
870 | prev_y = e.clientY;
871 | }
872 |
873 | function onMouseDrag(e) {
874 |
875 | var diff = prev_y - e.clientY;
876 | _this.setValue(_this.getValue() + diff * _this.__impliedStep);
877 |
878 | prev_y = e.clientY;
879 |
880 | }
881 |
882 | function onMouseUp() {
883 | dom.unbind(window, 'mousemove', onMouseDrag);
884 | dom.unbind(window, 'mouseup', onMouseUp);
885 | }
886 |
887 | this.updateDisplay();
888 |
889 | this.domElement.appendChild(this.__input);
890 |
891 | };
892 |
893 | NumberControllerBox.superclass = NumberController;
894 |
895 | common.extend(
896 |
897 | NumberControllerBox.prototype,
898 | NumberController.prototype,
899 |
900 | {
901 |
902 | updateDisplay: function() {
903 |
904 | this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision);
905 | return NumberControllerBox.superclass.prototype.updateDisplay.call(this);
906 | }
907 |
908 | }
909 |
910 | );
911 |
912 | function roundToDecimal(value, decimals) {
913 | var tenTo = Math.pow(10, decimals);
914 | return Math.round(value * tenTo) / tenTo;
915 | }
916 |
917 | return NumberControllerBox;
918 |
919 | })(dat.controllers.NumberController,
920 | dat.dom.dom,
921 | dat.utils.common);
922 |
923 |
924 | dat.controllers.NumberControllerSlider = (function (NumberController, dom, css, common, styleSheet) {
925 |
926 | /**
927 | * @class Represents a given property of an object that is a number, contains
928 | * a minimum and maximum, and provides a slider element with which to
929 | * manipulate it. It should be noted that the slider element is made up of
930 | * <div>
tags, not the html5
931 | * <slider>
element.
932 | *
933 | * @extends dat.controllers.Controller
934 | * @extends dat.controllers.NumberController
935 | *
936 | * @param {Object} object The object to be manipulated
937 | * @param {string} property The name of the property to be manipulated
938 | * @param {Number} minValue Minimum allowed value
939 | * @param {Number} maxValue Maximum allowed value
940 | * @param {Number} stepValue Increment by which to change value
941 | *
942 | * @member dat.controllers
943 | */
944 | var NumberControllerSlider = function(object, property, min, max, step) {
945 |
946 | NumberControllerSlider.superclass.call(this, object, property, { min: min, max: max, step: step });
947 |
948 | var _this = this;
949 |
950 | this.__background = document.createElement('div');
951 | this.__foreground = document.createElement('div');
952 |
953 |
954 |
955 | dom.bind(this.__background, 'mousedown', onMouseDown);
956 |
957 | dom.addClass(this.__background, 'slider');
958 | dom.addClass(this.__foreground, 'slider-fg');
959 |
960 | function onMouseDown(e) {
961 |
962 | dom.bind(window, 'mousemove', onMouseDrag);
963 | dom.bind(window, 'mouseup', onMouseUp);
964 |
965 | onMouseDrag(e);
966 | }
967 |
968 | function onMouseDrag(e) {
969 |
970 | e.preventDefault();
971 |
972 | var offset = dom.getOffset(_this.__background);
973 | var width = dom.getWidth(_this.__background);
974 |
975 | _this.setValue(
976 | map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max)
977 | );
978 |
979 | return false;
980 |
981 | }
982 |
983 | function onMouseUp() {
984 | dom.unbind(window, 'mousemove', onMouseDrag);
985 | dom.unbind(window, 'mouseup', onMouseUp);
986 | if (_this.__onFinishChange) {
987 | _this.__onFinishChange.call(_this, _this.getValue());
988 | }
989 | }
990 |
991 | this.updateDisplay();
992 |
993 | this.__background.appendChild(this.__foreground);
994 | this.domElement.appendChild(this.__background);
995 |
996 | };
997 |
998 | NumberControllerSlider.superclass = NumberController;
999 |
1000 | /**
1001 | * Injects default stylesheet for slider elements.
1002 | */
1003 | NumberControllerSlider.useDefaultStyles = function() {
1004 | css.inject(styleSheet);
1005 | };
1006 |
1007 | common.extend(
1008 |
1009 | NumberControllerSlider.prototype,
1010 | NumberController.prototype,
1011 |
1012 | {
1013 |
1014 | updateDisplay: function() {
1015 | var pct = (this.getValue() - this.__min)/(this.__max - this.__min);
1016 | this.__foreground.style.width = pct*100+'%';
1017 | return NumberControllerSlider.superclass.prototype.updateDisplay.call(this);
1018 | }
1019 |
1020 | }
1021 |
1022 |
1023 |
1024 | );
1025 |
1026 | function map(v, i1, i2, o1, o2) {
1027 | return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
1028 | }
1029 |
1030 | return NumberControllerSlider;
1031 |
1032 | })(dat.controllers.NumberController,
1033 | dat.dom.dom,
1034 | dat.utils.css,
1035 | dat.utils.common,
1036 | ".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}");
1037 |
1038 |
1039 | dat.controllers.FunctionController = (function (Controller, dom, common) {
1040 |
1041 | /**
1042 | * @class Provides a GUI interface to fire a specified method, a property of an object.
1043 | *
1044 | * @extends dat.controllers.Controller
1045 | *
1046 | * @param {Object} object The object to be manipulated
1047 | * @param {string} property The name of the property to be manipulated
1048 | *
1049 | * @member dat.controllers
1050 | */
1051 | var FunctionController = function(object, property, text) {
1052 |
1053 | FunctionController.superclass.call(this, object, property);
1054 |
1055 | var _this = this;
1056 |
1057 | this.__button = document.createElement('div');
1058 | this.__button.innerHTML = text === undefined ? 'Fire' : text;
1059 | dom.bind(this.__button, 'click', function(e) {
1060 | e.preventDefault();
1061 | _this.fire();
1062 | return false;
1063 | });
1064 |
1065 | dom.addClass(this.__button, 'button');
1066 |
1067 | this.domElement.appendChild(this.__button);
1068 |
1069 |
1070 | };
1071 |
1072 | FunctionController.superclass = Controller;
1073 |
1074 | common.extend(
1075 |
1076 | FunctionController.prototype,
1077 | Controller.prototype,
1078 | {
1079 |
1080 | fire: function() {
1081 | if (this.__onChange) {
1082 | this.__onChange.call(this);
1083 | }
1084 | if (this.__onFinishChange) {
1085 | this.__onFinishChange.call(this, this.getValue());
1086 | }
1087 | this.getValue().call(this.object);
1088 | }
1089 | }
1090 |
1091 | );
1092 |
1093 | return FunctionController;
1094 |
1095 | })(dat.controllers.Controller,
1096 | dat.dom.dom,
1097 | dat.utils.common);
1098 |
1099 |
1100 | dat.controllers.BooleanController = (function (Controller, dom, common) {
1101 |
1102 | /**
1103 | * @class Provides a checkbox input to alter the boolean property of an object.
1104 | * @extends dat.controllers.Controller
1105 | *
1106 | * @param {Object} object The object to be manipulated
1107 | * @param {string} property The name of the property to be manipulated
1108 | *
1109 | * @member dat.controllers
1110 | */
1111 | var BooleanController = function(object, property) {
1112 |
1113 | BooleanController.superclass.call(this, object, property);
1114 |
1115 | var _this = this;
1116 | this.__prev = this.getValue();
1117 |
1118 | this.__checkbox = document.createElement('input');
1119 | this.__checkbox.setAttribute('type', 'checkbox');
1120 |
1121 |
1122 | dom.bind(this.__checkbox, 'change', onChange, false);
1123 |
1124 | this.domElement.appendChild(this.__checkbox);
1125 |
1126 | // Match original value
1127 | this.updateDisplay();
1128 |
1129 | function onChange() {
1130 | _this.setValue(!_this.__prev);
1131 | }
1132 |
1133 | };
1134 |
1135 | BooleanController.superclass = Controller;
1136 |
1137 | common.extend(
1138 |
1139 | BooleanController.prototype,
1140 | Controller.prototype,
1141 |
1142 | {
1143 |
1144 | setValue: function(v) {
1145 | var toReturn = BooleanController.superclass.prototype.setValue.call(this, v);
1146 | if (this.__onFinishChange) {
1147 | this.__onFinishChange.call(this, this.getValue());
1148 | }
1149 | this.__prev = this.getValue();
1150 | return toReturn;
1151 | },
1152 |
1153 | updateDisplay: function() {
1154 |
1155 | if (this.getValue() === true) {
1156 | this.__checkbox.setAttribute('checked', 'checked');
1157 | this.__checkbox.checked = true;
1158 | } else {
1159 | this.__checkbox.checked = false;
1160 | }
1161 |
1162 | return BooleanController.superclass.prototype.updateDisplay.call(this);
1163 |
1164 | }
1165 |
1166 |
1167 | }
1168 |
1169 | );
1170 |
1171 | return BooleanController;
1172 |
1173 | })(dat.controllers.Controller,
1174 | dat.dom.dom,
1175 | dat.utils.common);
1176 |
1177 |
1178 | dat.color.toString = (function (common) {
1179 |
1180 | return function(color) {
1181 |
1182 | if (color.a == 1 || common.isUndefined(color.a)) {
1183 |
1184 | var s = color.hex.toString(16);
1185 | while (s.length < 6) {
1186 | s = '0' + s;
1187 | }
1188 |
1189 | return '#' + s;
1190 |
1191 | } else {
1192 |
1193 | return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')';
1194 |
1195 | }
1196 |
1197 | }
1198 |
1199 | })(dat.utils.common);
1200 |
1201 |
1202 | dat.color.interpret = (function (toString, common) {
1203 |
1204 | var result, toReturn;
1205 |
1206 | var interpret = function() {
1207 |
1208 | toReturn = false;
1209 |
1210 | var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0];
1211 |
1212 | common.each(INTERPRETATIONS, function(family) {
1213 |
1214 | if (family.litmus(original)) {
1215 |
1216 | common.each(family.conversions, function(conversion, conversionName) {
1217 |
1218 | result = conversion.read(original);
1219 |
1220 | if (toReturn === false && result !== false) {
1221 | toReturn = result;
1222 | result.conversionName = conversionName;
1223 | result.conversion = conversion;
1224 | return common.BREAK;
1225 |
1226 | }
1227 |
1228 | });
1229 |
1230 | return common.BREAK;
1231 |
1232 | }
1233 |
1234 | });
1235 |
1236 | return toReturn;
1237 |
1238 | };
1239 |
1240 | var INTERPRETATIONS = [
1241 |
1242 | // Strings
1243 | {
1244 |
1245 | litmus: common.isString,
1246 |
1247 | conversions: {
1248 |
1249 | THREE_CHAR_HEX: {
1250 |
1251 | read: function(original) {
1252 |
1253 | var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);
1254 | if (test === null) return false;
1255 |
1256 | return {
1257 | space: 'HEX',
1258 | hex: parseInt(
1259 | '0x' +
1260 | test[1].toString() + test[1].toString() +
1261 | test[2].toString() + test[2].toString() +
1262 | test[3].toString() + test[3].toString())
1263 | };
1264 |
1265 | },
1266 |
1267 | write: toString
1268 |
1269 | },
1270 |
1271 | SIX_CHAR_HEX: {
1272 |
1273 | read: function(original) {
1274 |
1275 | var test = original.match(/^#([A-F0-9]{6})$/i);
1276 | if (test === null) return false;
1277 |
1278 | return {
1279 | space: 'HEX',
1280 | hex: parseInt('0x' + test[1].toString())
1281 | };
1282 |
1283 | },
1284 |
1285 | write: toString
1286 |
1287 | },
1288 |
1289 | CSS_RGB: {
1290 |
1291 | read: function(original) {
1292 |
1293 | var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
1294 | if (test === null) return false;
1295 |
1296 | return {
1297 | space: 'RGB',
1298 | r: parseFloat(test[1]),
1299 | g: parseFloat(test[2]),
1300 | b: parseFloat(test[3])
1301 | };
1302 |
1303 | },
1304 |
1305 | write: toString
1306 |
1307 | },
1308 |
1309 | CSS_RGBA: {
1310 |
1311 | read: function(original) {
1312 |
1313 | var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);
1314 | if (test === null) return false;
1315 |
1316 | return {
1317 | space: 'RGB',
1318 | r: parseFloat(test[1]),
1319 | g: parseFloat(test[2]),
1320 | b: parseFloat(test[3]),
1321 | a: parseFloat(test[4])
1322 | };
1323 |
1324 | },
1325 |
1326 | write: toString
1327 |
1328 | }
1329 |
1330 | }
1331 |
1332 | },
1333 |
1334 | // Numbers
1335 | {
1336 |
1337 | litmus: common.isNumber,
1338 |
1339 | conversions: {
1340 |
1341 | HEX: {
1342 | read: function(original) {
1343 | return {
1344 | space: 'HEX',
1345 | hex: original,
1346 | conversionName: 'HEX'
1347 | }
1348 | },
1349 |
1350 | write: function(color) {
1351 | return color.hex;
1352 | }
1353 | }
1354 |
1355 | }
1356 |
1357 | },
1358 |
1359 | // Arrays
1360 | {
1361 |
1362 | litmus: common.isArray,
1363 |
1364 | conversions: {
1365 |
1366 | RGB_ARRAY: {
1367 | read: function(original) {
1368 | if (original.length != 3) return false;
1369 | return {
1370 | space: 'RGB',
1371 | r: original[0],
1372 | g: original[1],
1373 | b: original[2]
1374 | };
1375 | },
1376 |
1377 | write: function(color) {
1378 | return [color.r, color.g, color.b];
1379 | }
1380 |
1381 | },
1382 |
1383 | RGBA_ARRAY: {
1384 | read: function(original) {
1385 | if (original.length != 4) return false;
1386 | return {
1387 | space: 'RGB',
1388 | r: original[0],
1389 | g: original[1],
1390 | b: original[2],
1391 | a: original[3]
1392 | };
1393 | },
1394 |
1395 | write: function(color) {
1396 | return [color.r, color.g, color.b, color.a];
1397 | }
1398 |
1399 | }
1400 |
1401 | }
1402 |
1403 | },
1404 |
1405 | // Objects
1406 | {
1407 |
1408 | litmus: common.isObject,
1409 |
1410 | conversions: {
1411 |
1412 | RGBA_OBJ: {
1413 | read: function(original) {
1414 | if (common.isNumber(original.r) &&
1415 | common.isNumber(original.g) &&
1416 | common.isNumber(original.b) &&
1417 | common.isNumber(original.a)) {
1418 | return {
1419 | space: 'RGB',
1420 | r: original.r,
1421 | g: original.g,
1422 | b: original.b,
1423 | a: original.a
1424 | }
1425 | }
1426 | return false;
1427 | },
1428 |
1429 | write: function(color) {
1430 | return {
1431 | r: color.r,
1432 | g: color.g,
1433 | b: color.b,
1434 | a: color.a
1435 | }
1436 | }
1437 | },
1438 |
1439 | RGB_OBJ: {
1440 | read: function(original) {
1441 | if (common.isNumber(original.r) &&
1442 | common.isNumber(original.g) &&
1443 | common.isNumber(original.b)) {
1444 | return {
1445 | space: 'RGB',
1446 | r: original.r,
1447 | g: original.g,
1448 | b: original.b
1449 | }
1450 | }
1451 | return false;
1452 | },
1453 |
1454 | write: function(color) {
1455 | return {
1456 | r: color.r,
1457 | g: color.g,
1458 | b: color.b
1459 | }
1460 | }
1461 | },
1462 |
1463 | HSVA_OBJ: {
1464 | read: function(original) {
1465 | if (common.isNumber(original.h) &&
1466 | common.isNumber(original.s) &&
1467 | common.isNumber(original.v) &&
1468 | common.isNumber(original.a)) {
1469 | return {
1470 | space: 'HSV',
1471 | h: original.h,
1472 | s: original.s,
1473 | v: original.v,
1474 | a: original.a
1475 | }
1476 | }
1477 | return false;
1478 | },
1479 |
1480 | write: function(color) {
1481 | return {
1482 | h: color.h,
1483 | s: color.s,
1484 | v: color.v,
1485 | a: color.a
1486 | }
1487 | }
1488 | },
1489 |
1490 | HSV_OBJ: {
1491 | read: function(original) {
1492 | if (common.isNumber(original.h) &&
1493 | common.isNumber(original.s) &&
1494 | common.isNumber(original.v)) {
1495 | return {
1496 | space: 'HSV',
1497 | h: original.h,
1498 | s: original.s,
1499 | v: original.v
1500 | }
1501 | }
1502 | return false;
1503 | },
1504 |
1505 | write: function(color) {
1506 | return {
1507 | h: color.h,
1508 | s: color.s,
1509 | v: color.v
1510 | }
1511 | }
1512 |
1513 | }
1514 |
1515 | }
1516 |
1517 | }
1518 |
1519 |
1520 | ];
1521 |
1522 | return interpret;
1523 |
1524 |
1525 | })(dat.color.toString,
1526 | dat.utils.common);
1527 |
1528 |
1529 | dat.GUI = dat.gui.GUI = (function (css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) {
1530 |
1531 | css.inject(styleSheet);
1532 |
1533 | /** Outer-most className for GUI's */
1534 | var CSS_NAMESPACE = 'dg';
1535 |
1536 | var HIDE_KEY_CODE = 72;
1537 |
1538 | /** The only value shared between the JS and SCSS. Use caution. */
1539 | var CLOSE_BUTTON_HEIGHT = 20;
1540 |
1541 | var DEFAULT_DEFAULT_PRESET_NAME = 'Default';
1542 |
1543 | var SUPPORTS_LOCAL_STORAGE = (function() {
1544 | try {
1545 | return 'localStorage' in window && window['localStorage'] !== null;
1546 | } catch (e) {
1547 | return false;
1548 | }
1549 | })();
1550 |
1551 | var SAVE_DIALOGUE;
1552 |
1553 | /** Have we yet to create an autoPlace GUI? */
1554 | var auto_place_virgin = true;
1555 |
1556 | /** Fixed position div that auto place GUI's go inside */
1557 | var auto_place_container;
1558 |
1559 | /** Are we hiding the GUI's ? */
1560 | var hide = false;
1561 |
1562 | /** GUI's which should be hidden */
1563 | var hideable_guis = [];
1564 |
1565 | /**
1566 | * A lightweight controller library for JavaScript. It allows you to easily
1567 | * manipulate variables and fire functions on the fly.
1568 | * @class
1569 | *
1570 | * @member dat.gui
1571 | *
1572 | * @param {Object} [params]
1573 | * @param {String} [params.name] The name of this GUI.
1574 | * @param {Object} [params.load] JSON object representing the saved state of
1575 | * this GUI.
1576 | * @param {Boolean} [params.auto=true]
1577 | * @param {dat.gui.GUI} [params.parent] The GUI I'm nested in.
1578 | * @param {Boolean} [params.closed] If true, starts closed
1579 | */
1580 | var GUI = function(params) {
1581 |
1582 | var _this = this;
1583 |
1584 | /**
1585 | * Outermost DOM Element
1586 | * @type DOMElement
1587 | */
1588 | this.domElement = document.createElement('div');
1589 | this.__ul = document.createElement('ul');
1590 | this.domElement.appendChild(this.__ul);
1591 |
1592 | dom.addClass(this.domElement, CSS_NAMESPACE);
1593 |
1594 | /**
1595 | * Nested GUI's by name
1596 | * @ignore
1597 | */
1598 | this.__folders = {};
1599 |
1600 | this.__controllers = [];
1601 |
1602 | /**
1603 | * List of objects I'm remembering for save, only used in top level GUI
1604 | * @ignore
1605 | */
1606 | this.__rememberedObjects = [];
1607 |
1608 | /**
1609 | * Maps the index of remembered objects to a map of controllers, only used
1610 | * in top level GUI.
1611 | *
1612 | * @private
1613 | * @ignore
1614 | *
1615 | * @example
1616 | * [
1617 | * {
1618 | * propertyName: Controller,
1619 | * anotherPropertyName: Controller
1620 | * },
1621 | * {
1622 | * propertyName: Controller
1623 | * }
1624 | * ]
1625 | */
1626 | this.__rememberedObjectIndecesToControllers = [];
1627 |
1628 | this.__listening = [];
1629 |
1630 | params = params || {};
1631 |
1632 | // Default parameters
1633 | params = common.defaults(params, {
1634 | autoPlace: true,
1635 | width: GUI.DEFAULT_WIDTH
1636 | });
1637 |
1638 | params = common.defaults(params, {
1639 | resizable: params.autoPlace,
1640 | hideable: params.autoPlace
1641 | });
1642 |
1643 |
1644 | if (!common.isUndefined(params.load)) {
1645 |
1646 | // Explicit preset
1647 | if (params.preset) params.load.preset = params.preset;
1648 |
1649 | } else {
1650 |
1651 | params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME };
1652 |
1653 | }
1654 |
1655 | if (common.isUndefined(params.parent) && params.hideable) {
1656 | hideable_guis.push(this);
1657 | }
1658 |
1659 | // Only root level GUI's are resizable.
1660 | params.resizable = common.isUndefined(params.parent) && params.resizable;
1661 |
1662 |
1663 | if (params.autoPlace && common.isUndefined(params.scrollable)) {
1664 | params.scrollable = true;
1665 | }
1666 | // params.scrollable = common.isUndefined(params.parent) && params.scrollable === true;
1667 |
1668 | // Not part of params because I don't want people passing this in via
1669 | // constructor. Should be a 'remembered' value.
1670 | var use_local_storage =
1671 | SUPPORTS_LOCAL_STORAGE &&
1672 | localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true';
1673 |
1674 | Object.defineProperties(this,
1675 |
1676 | /** @lends dat.gui.GUI.prototype */
1677 | {
1678 |
1679 | /**
1680 | * The parent GUI
1681 | * @type dat.gui.GUI
1682 | */
1683 | parent: {
1684 | get: function() {
1685 | return params.parent;
1686 | }
1687 | },
1688 |
1689 | scrollable: {
1690 | get: function() {
1691 | return params.scrollable;
1692 | }
1693 | },
1694 |
1695 | /**
1696 | * Handles GUI
's element placement for you
1697 | * @type Boolean
1698 | */
1699 | autoPlace: {
1700 | get: function() {
1701 | return params.autoPlace;
1702 | }
1703 | },
1704 |
1705 | /**
1706 | * The identifier for a set of saved values
1707 | * @type String
1708 | */
1709 | preset: {
1710 |
1711 | get: function() {
1712 | if (_this.parent) {
1713 | return _this.getRoot().preset;
1714 | } else {
1715 | return params.load.preset;
1716 | }
1717 | },
1718 |
1719 | set: function(v) {
1720 | if (_this.parent) {
1721 | _this.getRoot().preset = v;
1722 | } else {
1723 | params.load.preset = v;
1724 | }
1725 | setPresetSelectIndex(this);
1726 | _this.revert();
1727 | }
1728 |
1729 | },
1730 |
1731 | /**
1732 | * The width of GUI
element
1733 | * @type Number
1734 | */
1735 | width: {
1736 | get: function() {
1737 | return params.width;
1738 | },
1739 | set: function(v) {
1740 | params.width = v;
1741 | setWidth(_this, v);
1742 | }
1743 | },
1744 |
1745 | /**
1746 | * The name of GUI
. Used for folders. i.e
1747 | * a folder's name
1748 | * @type String
1749 | */
1750 | name: {
1751 | get: function() {
1752 | return params.name;
1753 | },
1754 | set: function(v) {
1755 | // TODO Check for collisions among sibling folders
1756 | params.name = v;
1757 | if (title_row_name) {
1758 | title_row_name.innerHTML = params.name;
1759 | }
1760 | }
1761 | },
1762 |
1763 | /**
1764 | * Whether the GUI
is collapsed or not
1765 | * @type Boolean
1766 | */
1767 | closed: {
1768 | get: function() {
1769 | return params.closed;
1770 | },
1771 | set: function(v) {
1772 | params.closed = v;
1773 | if (params.closed) {
1774 | dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
1775 | } else {
1776 | dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
1777 | }
1778 | // For browsers that aren't going to respect the CSS transition,
1779 | // Lets just check our height against the window height right off
1780 | // the bat.
1781 | this.onResize();
1782 |
1783 | if (_this.__closeButton) {
1784 | _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED;
1785 | }
1786 | }
1787 | },
1788 |
1789 | /**
1790 | * Contains all presets
1791 | * @type Object
1792 | */
1793 | load: {
1794 | get: function() {
1795 | return params.load;
1796 | }
1797 | },
1798 |
1799 | /**
1800 | * Determines whether or not to use localStorage as the means for
1801 | * remember
ing
1802 | * @type Boolean
1803 | */
1804 | useLocalStorage: {
1805 |
1806 | get: function() {
1807 | return use_local_storage;
1808 | },
1809 | set: function(bool) {
1810 | if (SUPPORTS_LOCAL_STORAGE) {
1811 | use_local_storage = bool;
1812 | if (bool) {
1813 | dom.bind(window, 'unload', saveToLocalStorage);
1814 | } else {
1815 | dom.unbind(window, 'unload', saveToLocalStorage);
1816 | }
1817 | localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool);
1818 | }
1819 | }
1820 |
1821 | }
1822 |
1823 | });
1824 |
1825 | // Are we a root level GUI?
1826 | if (common.isUndefined(params.parent)) {
1827 |
1828 | params.closed = false;
1829 |
1830 | dom.addClass(this.domElement, GUI.CLASS_MAIN);
1831 | dom.makeSelectable(this.domElement, false);
1832 |
1833 | // Are we supposed to be loading locally?
1834 | if (SUPPORTS_LOCAL_STORAGE) {
1835 |
1836 | if (use_local_storage) {
1837 |
1838 | _this.useLocalStorage = true;
1839 |
1840 | var saved_gui = localStorage.getItem(getLocalStorageHash(this, 'gui'));
1841 |
1842 | if (saved_gui) {
1843 | params.load = JSON.parse(saved_gui);
1844 | }
1845 |
1846 | }
1847 |
1848 | }
1849 |
1850 | this.__closeButton = document.createElement('div');
1851 | this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
1852 | dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
1853 | this.domElement.appendChild(this.__closeButton);
1854 |
1855 | dom.bind(this.__closeButton, 'click', function() {
1856 |
1857 | _this.closed = !_this.closed;
1858 |
1859 |
1860 | });
1861 |
1862 |
1863 | // Oh, you're a nested GUI!
1864 | } else {
1865 |
1866 | if (params.closed === undefined) {
1867 | params.closed = true;
1868 | }
1869 |
1870 | var title_row_name = document.createTextNode(params.name);
1871 | dom.addClass(title_row_name, 'controller-name');
1872 |
1873 | var title_row = addRow(_this, title_row_name);
1874 |
1875 | var on_click_title = function(e) {
1876 | e.preventDefault();
1877 | _this.closed = !_this.closed;
1878 | return false;
1879 | };
1880 |
1881 | dom.addClass(this.__ul, GUI.CLASS_CLOSED);
1882 |
1883 | dom.addClass(title_row, 'title');
1884 | dom.bind(title_row, 'click', on_click_title);
1885 |
1886 | if (!params.closed) {
1887 | this.closed = false;
1888 | }
1889 |
1890 | }
1891 |
1892 | if (params.autoPlace) {
1893 |
1894 | if (common.isUndefined(params.parent)) {
1895 |
1896 | if (auto_place_virgin) {
1897 | auto_place_container = document.createElement('div');
1898 | dom.addClass(auto_place_container, CSS_NAMESPACE);
1899 | dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER);
1900 | document.body.appendChild(auto_place_container);
1901 | auto_place_virgin = false;
1902 | }
1903 |
1904 | // Put it in the dom for you.
1905 | auto_place_container.appendChild(this.domElement);
1906 |
1907 | // Apply the auto styles
1908 | dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
1909 |
1910 | }
1911 |
1912 |
1913 | // Make it not elastic.
1914 | if (!this.parent) setWidth(_this, params.width);
1915 |
1916 | }
1917 |
1918 | dom.bind(window, 'resize', function() { _this.onResize() });
1919 | dom.bind(this.__ul, 'webkitTransitionEnd', function() { _this.onResize(); });
1920 | dom.bind(this.__ul, 'transitionend', function() { _this.onResize() });
1921 | dom.bind(this.__ul, 'oTransitionEnd', function() { _this.onResize() });
1922 | this.onResize();
1923 |
1924 |
1925 | if (params.resizable) {
1926 | addResizeHandle(this);
1927 | }
1928 |
1929 | function saveToLocalStorage() {
1930 | localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject()));
1931 | }
1932 |
1933 | var root = _this.getRoot();
1934 | function resetWidth() {
1935 | var root = _this.getRoot();
1936 | root.width += 1;
1937 | common.defer(function() {
1938 | root.width -= 1;
1939 | });
1940 | }
1941 |
1942 | if (!params.parent) {
1943 | resetWidth();
1944 | }
1945 |
1946 | };
1947 |
1948 | GUI.toggleHide = function() {
1949 |
1950 | hide = !hide;
1951 | common.each(hideable_guis, function(gui) {
1952 | gui.domElement.style.zIndex = hide ? -999 : 999;
1953 | gui.domElement.style.opacity = hide ? 0 : 1;
1954 | });
1955 | };
1956 |
1957 | GUI.CLASS_AUTO_PLACE = 'a';
1958 | GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac';
1959 | GUI.CLASS_MAIN = 'main';
1960 | GUI.CLASS_CONTROLLER_ROW = 'cr';
1961 | GUI.CLASS_TOO_TALL = 'taller-than-window';
1962 | GUI.CLASS_CLOSED = 'closed';
1963 | GUI.CLASS_CLOSE_BUTTON = 'close-button';
1964 | GUI.CLASS_DRAG = 'drag';
1965 |
1966 | GUI.DEFAULT_WIDTH = 245;
1967 | GUI.TEXT_CLOSED = 'Close Controls';
1968 | GUI.TEXT_OPEN = 'Open Controls';
1969 |
1970 | dom.bind(window, 'keydown', function(e) {
1971 |
1972 | if (document.activeElement.type !== 'text' &&
1973 | (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) {
1974 | GUI.toggleHide();
1975 | }
1976 |
1977 | }, false);
1978 |
1979 | common.extend(
1980 |
1981 | GUI.prototype,
1982 |
1983 | /** @lends dat.gui.GUI */
1984 | {
1985 |
1986 | /**
1987 | * @param object
1988 | * @param property
1989 | * @returns {dat.controllers.Controller} The new controller that was added.
1990 | * @instance
1991 | */
1992 | add: function(object, property) {
1993 |
1994 | return add(
1995 | this,
1996 | object,
1997 | property,
1998 | {
1999 | factoryArgs: Array.prototype.slice.call(arguments, 2)
2000 | }
2001 | );
2002 |
2003 | },
2004 |
2005 | /**
2006 | * @param object
2007 | * @param property
2008 | * @returns {dat.controllers.ColorController} The new controller that was added.
2009 | * @instance
2010 | */
2011 | addColor: function(object, property) {
2012 |
2013 | return add(
2014 | this,
2015 | object,
2016 | property,
2017 | {
2018 | color: true
2019 | }
2020 | );
2021 |
2022 | },
2023 |
2024 | /**
2025 | * @param controller
2026 | * @instance
2027 | */
2028 | remove: function(controller) {
2029 |
2030 | // TODO listening?
2031 | this.__ul.removeChild(controller.__li);
2032 | this.__controllers.slice(this.__controllers.indexOf(controller), 1);
2033 | var _this = this;
2034 | common.defer(function() {
2035 | _this.onResize();
2036 | });
2037 |
2038 | },
2039 |
2040 | destroy: function() {
2041 |
2042 | if (this.autoPlace) {
2043 | auto_place_container.removeChild(this.domElement);
2044 | }
2045 |
2046 | },
2047 |
2048 | /**
2049 | * @param name
2050 | * @returns {dat.gui.GUI} The new folder.
2051 | * @throws {Error} if this GUI already has a folder by the specified
2052 | * name
2053 | * @instance
2054 | */
2055 | addFolder: function(name) {
2056 |
2057 | // We have to prevent collisions on names in order to have a key
2058 | // by which to remember saved values
2059 | if (this.__folders[name] !== undefined) {
2060 | throw new Error('You already have a folder in this GUI by the' +
2061 | ' name "' + name + '"');
2062 | }
2063 |
2064 | var new_gui_params = { name: name, parent: this };
2065 |
2066 | // We need to pass down the autoPlace trait so that we can
2067 | // attach event listeners to open/close folder actions to
2068 | // ensure that a scrollbar appears if the window is too short.
2069 | new_gui_params.autoPlace = this.autoPlace;
2070 |
2071 | // Do we have saved appearance data for this folder?
2072 |
2073 | if (this.load && // Anything loaded?
2074 | this.load.folders && // Was my parent a dead-end?
2075 | this.load.folders[name]) { // Did daddy remember me?
2076 |
2077 | // Start me closed if I was closed
2078 | new_gui_params.closed = this.load.folders[name].closed;
2079 |
2080 | // Pass down the loaded data
2081 | new_gui_params.load = this.load.folders[name];
2082 |
2083 | }
2084 |
2085 | var gui = new GUI(new_gui_params);
2086 | this.__folders[name] = gui;
2087 |
2088 | var li = addRow(this, gui.domElement);
2089 | dom.addClass(li, 'folder');
2090 | return gui;
2091 |
2092 | },
2093 |
2094 | open: function() {
2095 | this.closed = false;
2096 | },
2097 |
2098 | close: function() {
2099 | this.closed = true;
2100 | },
2101 |
2102 | onResize: function() {
2103 |
2104 | var root = this.getRoot();
2105 |
2106 | if (root.scrollable) {
2107 |
2108 | var top = dom.getOffset(root.__ul).top;
2109 | var h = 0;
2110 |
2111 | common.each(root.__ul.childNodes, function(node) {
2112 | if (! (root.autoPlace && node === root.__save_row))
2113 | h += dom.getHeight(node);
2114 | });
2115 |
2116 | if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) {
2117 | dom.addClass(root.domElement, GUI.CLASS_TOO_TALL);
2118 | root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px';
2119 | } else {
2120 | dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL);
2121 | root.__ul.style.height = 'auto';
2122 | }
2123 |
2124 | }
2125 |
2126 | if (root.__resize_handle) {
2127 | common.defer(function() {
2128 | root.__resize_handle.style.height = root.__ul.offsetHeight + 'px';
2129 | });
2130 | }
2131 |
2132 | if (root.__closeButton) {
2133 | root.__closeButton.style.width = root.width + 'px';
2134 | }
2135 |
2136 | },
2137 |
2138 | /**
2139 | * Mark objects for saving. The order of these objects cannot change as
2140 | * the GUI grows. When remembering new objects, append them to the end
2141 | * of the list.
2142 | *
2143 | * @param {Object...} objects
2144 | * @throws {Error} if not called on a top level GUI.
2145 | * @instance
2146 | */
2147 | remember: function() {
2148 |
2149 | if (common.isUndefined(SAVE_DIALOGUE)) {
2150 | SAVE_DIALOGUE = new CenteredDiv();
2151 | SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents;
2152 | }
2153 |
2154 | if (this.parent) {
2155 | throw new Error("You can only call remember on a top level GUI.");
2156 | }
2157 |
2158 | var _this = this;
2159 |
2160 | common.each(Array.prototype.slice.call(arguments), function(object) {
2161 | if (_this.__rememberedObjects.length == 0) {
2162 | addSaveMenu(_this);
2163 | }
2164 | if (_this.__rememberedObjects.indexOf(object) == -1) {
2165 | _this.__rememberedObjects.push(object);
2166 | }
2167 | });
2168 |
2169 | if (this.autoPlace) {
2170 | // Set save row width
2171 | setWidth(this, this.width);
2172 | }
2173 |
2174 | },
2175 |
2176 | /**
2177 | * @returns {dat.gui.GUI} the topmost parent GUI of a nested GUI.
2178 | * @instance
2179 | */
2180 | getRoot: function() {
2181 | var gui = this;
2182 | while (gui.parent) {
2183 | gui = gui.parent;
2184 | }
2185 | return gui;
2186 | },
2187 |
2188 | /**
2189 | * @returns {Object} a JSON object representing the current state of
2190 | * this GUI as well as its remembered properties.
2191 | * @instance
2192 | */
2193 | getSaveObject: function() {
2194 |
2195 | var toReturn = this.load;
2196 |
2197 | toReturn.closed = this.closed;
2198 |
2199 | // Am I remembering any values?
2200 | if (this.__rememberedObjects.length > 0) {
2201 |
2202 | toReturn.preset = this.preset;
2203 |
2204 | if (!toReturn.remembered) {
2205 | toReturn.remembered = {};
2206 | }
2207 |
2208 | toReturn.remembered[this.preset] = getCurrentPreset(this);
2209 |
2210 | }
2211 |
2212 | toReturn.folders = {};
2213 | common.each(this.__folders, function(element, key) {
2214 | toReturn.folders[key] = element.getSaveObject();
2215 | });
2216 |
2217 | return toReturn;
2218 |
2219 | },
2220 |
2221 | save: function() {
2222 |
2223 | if (!this.load.remembered) {
2224 | this.load.remembered = {};
2225 | }
2226 |
2227 | this.load.remembered[this.preset] = getCurrentPreset(this);
2228 | markPresetModified(this, false);
2229 |
2230 | },
2231 |
2232 | saveAs: function(presetName) {
2233 |
2234 | if (!this.load.remembered) {
2235 |
2236 | // Retain default values upon first save
2237 | this.load.remembered = {};
2238 | this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true);
2239 |
2240 | }
2241 |
2242 | this.load.remembered[presetName] = getCurrentPreset(this);
2243 | this.preset = presetName;
2244 | addPresetOption(this, presetName, true);
2245 |
2246 | },
2247 |
2248 | revert: function(gui) {
2249 |
2250 | common.each(this.__controllers, function(controller) {
2251 | // Make revert work on Default.
2252 | if (!this.getRoot().load.remembered) {
2253 | controller.setValue(controller.initialValue);
2254 | } else {
2255 | recallSavedValue(gui || this.getRoot(), controller);
2256 | }
2257 | }, this);
2258 |
2259 | common.each(this.__folders, function(folder) {
2260 | folder.revert(folder);
2261 | });
2262 |
2263 | if (!gui) {
2264 | markPresetModified(this.getRoot(), false);
2265 | }
2266 |
2267 |
2268 | },
2269 |
2270 | listen: function(controller) {
2271 |
2272 | var init = this.__listening.length == 0;
2273 | this.__listening.push(controller);
2274 | if (init) updateDisplays(this.__listening);
2275 |
2276 | }
2277 |
2278 | }
2279 |
2280 | );
2281 |
2282 | function add(gui, object, property, params) {
2283 |
2284 | if (object[property] === undefined) {
2285 | throw new Error("Object " + object + " has no property \"" + property + "\"");
2286 | }
2287 |
2288 | var controller;
2289 |
2290 | if (params.color) {
2291 |
2292 | controller = new ColorController(object, property);
2293 |
2294 | } else {
2295 |
2296 | var factoryArgs = [object,property].concat(params.factoryArgs);
2297 | controller = controllerFactory.apply(gui, factoryArgs);
2298 |
2299 | }
2300 |
2301 | if (params.before instanceof Controller) {
2302 | params.before = params.before.__li;
2303 | }
2304 |
2305 | recallSavedValue(gui, controller);
2306 |
2307 | dom.addClass(controller.domElement, 'c');
2308 |
2309 | var name = document.createElement('span');
2310 | dom.addClass(name, 'property-name');
2311 | name.innerHTML = controller.property;
2312 |
2313 | var container = document.createElement('div');
2314 | container.appendChild(name);
2315 | container.appendChild(controller.domElement);
2316 |
2317 | var li = addRow(gui, container, params.before);
2318 |
2319 | dom.addClass(li, GUI.CLASS_CONTROLLER_ROW);
2320 | dom.addClass(li, typeof controller.getValue());
2321 |
2322 | augmentController(gui, li, controller);
2323 |
2324 | gui.__controllers.push(controller);
2325 |
2326 | return controller;
2327 |
2328 | }
2329 |
2330 | /**
2331 | * Add a row to the end of the GUI or before another row.
2332 | *
2333 | * @param gui
2334 | * @param [dom] If specified, inserts the dom content in the new row
2335 | * @param [liBefore] If specified, places the new row before another row
2336 | */
2337 | function addRow(gui, dom, liBefore) {
2338 | var li = document.createElement('li');
2339 | if (dom) li.appendChild(dom);
2340 | if (liBefore) {
2341 | gui.__ul.insertBefore(li, params.before);
2342 | } else {
2343 | gui.__ul.appendChild(li);
2344 | }
2345 | gui.onResize();
2346 | return li;
2347 | }
2348 |
2349 | function augmentController(gui, li, controller) {
2350 |
2351 | controller.__li = li;
2352 | controller.__gui = gui;
2353 |
2354 | common.extend(controller, {
2355 |
2356 | options: function(options) {
2357 |
2358 | if (arguments.length > 1) {
2359 | controller.remove();
2360 |
2361 | return add(
2362 | gui,
2363 | controller.object,
2364 | controller.property,
2365 | {
2366 | before: controller.__li.nextElementSibling,
2367 | factoryArgs: [common.toArray(arguments)]
2368 | }
2369 | );
2370 |
2371 | }
2372 |
2373 | if (common.isArray(options) || common.isObject(options)) {
2374 | controller.remove();
2375 |
2376 | return add(
2377 | gui,
2378 | controller.object,
2379 | controller.property,
2380 | {
2381 | before: controller.__li.nextElementSibling,
2382 | factoryArgs: [options]
2383 | }
2384 | );
2385 |
2386 | }
2387 |
2388 | },
2389 |
2390 | name: function(v) {
2391 | controller.__li.firstElementChild.firstElementChild.innerHTML = v;
2392 | return controller;
2393 | },
2394 |
2395 | listen: function() {
2396 | controller.__gui.listen(controller);
2397 | return controller;
2398 | },
2399 |
2400 | remove: function() {
2401 | controller.__gui.remove(controller);
2402 | return controller;
2403 | }
2404 |
2405 | });
2406 |
2407 | // All sliders should be accompanied by a box.
2408 | if (controller instanceof NumberControllerSlider) {
2409 |
2410 | var box = new NumberControllerBox(controller.object, controller.property,
2411 | { min: controller.__min, max: controller.__max, step: controller.__step });
2412 |
2413 | common.each(['updateDisplay', 'onChange', 'onFinishChange'], function(method) {
2414 | var pc = controller[method];
2415 | var pb = box[method];
2416 | controller[method] = box[method] = function() {
2417 | var args = Array.prototype.slice.call(arguments);
2418 | pc.apply(controller, args);
2419 | return pb.apply(box, args);
2420 | }
2421 | });
2422 |
2423 | dom.addClass(li, 'has-slider');
2424 | controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild);
2425 |
2426 | }
2427 | else if (controller instanceof NumberControllerBox) {
2428 |
2429 | var r = function(returned) {
2430 |
2431 | // Have we defined both boundaries?
2432 | if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) {
2433 |
2434 | // Well, then lets just replace this with a slider.
2435 | controller.remove();
2436 | return add(
2437 | gui,
2438 | controller.object,
2439 | controller.property,
2440 | {
2441 | before: controller.__li.nextElementSibling,
2442 | factoryArgs: [controller.__min, controller.__max, controller.__step]
2443 | });
2444 |
2445 | }
2446 |
2447 | return returned;
2448 |
2449 | };
2450 |
2451 | controller.min = common.compose(r, controller.min);
2452 | controller.max = common.compose(r, controller.max);
2453 |
2454 | }
2455 | else if (controller instanceof BooleanController) {
2456 |
2457 | dom.bind(li, 'click', function() {
2458 | dom.fakeEvent(controller.__checkbox, 'click');
2459 | });
2460 |
2461 | dom.bind(controller.__checkbox, 'click', function(e) {
2462 | e.stopPropagation(); // Prevents double-toggle
2463 | })
2464 |
2465 | }
2466 | else if (controller instanceof FunctionController) {
2467 |
2468 | dom.bind(li, 'click', function() {
2469 | dom.fakeEvent(controller.__button, 'click');
2470 | });
2471 |
2472 | dom.bind(li, 'mouseover', function() {
2473 | dom.addClass(controller.__button, 'hover');
2474 | });
2475 |
2476 | dom.bind(li, 'mouseout', function() {
2477 | dom.removeClass(controller.__button, 'hover');
2478 | });
2479 |
2480 | }
2481 | else if (controller instanceof ColorController) {
2482 |
2483 | dom.addClass(li, 'color');
2484 | controller.updateDisplay = common.compose(function(r) {
2485 | li.style.borderLeftColor = controller.__color.toString();
2486 | return r;
2487 | }, controller.updateDisplay);
2488 |
2489 | controller.updateDisplay();
2490 |
2491 | }
2492 |
2493 | controller.setValue = common.compose(function(r) {
2494 | if (gui.getRoot().__preset_select && controller.isModified()) {
2495 | markPresetModified(gui.getRoot(), true);
2496 | }
2497 | return r;
2498 | }, controller.setValue);
2499 |
2500 | }
2501 |
2502 | function recallSavedValue(gui, controller) {
2503 |
2504 | // Find the topmost GUI, that's where remembered objects live.
2505 | var root = gui.getRoot();
2506 |
2507 | // Does the object we're controlling match anything we've been told to
2508 | // remember?
2509 | var matched_index = root.__rememberedObjects.indexOf(controller.object);
2510 |
2511 | // Why yes, it does!
2512 | if (matched_index != -1) {
2513 |
2514 | // Let me fetch a map of controllers for thcommon.isObject.
2515 | var controller_map =
2516 | root.__rememberedObjectIndecesToControllers[matched_index];
2517 |
2518 | // Ohp, I believe this is the first controller we've created for this
2519 | // object. Lets make the map fresh.
2520 | if (controller_map === undefined) {
2521 | controller_map = {};
2522 | root.__rememberedObjectIndecesToControllers[matched_index] =
2523 | controller_map;
2524 | }
2525 |
2526 | // Keep track of this controller
2527 | controller_map[controller.property] = controller;
2528 |
2529 | // Okay, now have we saved any values for this controller?
2530 | if (root.load && root.load.remembered) {
2531 |
2532 | var preset_map = root.load.remembered;
2533 |
2534 | // Which preset are we trying to load?
2535 | var preset;
2536 |
2537 | if (preset_map[gui.preset]) {
2538 |
2539 | preset = preset_map[gui.preset];
2540 |
2541 | } else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) {
2542 |
2543 | // Uhh, you can have the default instead?
2544 | preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME];
2545 |
2546 | } else {
2547 |
2548 | // Nada.
2549 |
2550 | return;
2551 |
2552 | }
2553 |
2554 |
2555 | // Did the loaded object remember thcommon.isObject?
2556 | if (preset[matched_index] &&
2557 |
2558 | // Did we remember this particular property?
2559 | preset[matched_index][controller.property] !== undefined) {
2560 |
2561 | // We did remember something for this guy ...
2562 | var value = preset[matched_index][controller.property];
2563 |
2564 | // And that's what it is.
2565 | controller.initialValue = value;
2566 | controller.setValue(value);
2567 |
2568 | }
2569 |
2570 | }
2571 |
2572 | }
2573 |
2574 | }
2575 |
2576 | function getLocalStorageHash(gui, key) {
2577 | // TODO how does this deal with multiple GUI's?
2578 | return document.location.href + '.' + key;
2579 |
2580 | }
2581 |
2582 | function addSaveMenu(gui) {
2583 |
2584 | var div = gui.__save_row = document.createElement('li');
2585 |
2586 | dom.addClass(gui.domElement, 'has-save');
2587 |
2588 | gui.__ul.insertBefore(div, gui.__ul.firstChild);
2589 |
2590 | dom.addClass(div, 'save-row');
2591 |
2592 | var gears = document.createElement('span');
2593 | gears.innerHTML = ' ';
2594 | dom.addClass(gears, 'button gears');
2595 |
2596 | // TODO replace with FunctionController
2597 | var button = document.createElement('span');
2598 | button.innerHTML = 'Save';
2599 | dom.addClass(button, 'button');
2600 | dom.addClass(button, 'save');
2601 |
2602 | var button2 = document.createElement('span');
2603 | button2.innerHTML = 'New';
2604 | dom.addClass(button2, 'button');
2605 | dom.addClass(button2, 'save-as');
2606 |
2607 | var button3 = document.createElement('span');
2608 | button3.innerHTML = 'Revert';
2609 | dom.addClass(button3, 'button');
2610 | dom.addClass(button3, 'revert');
2611 |
2612 | var select = gui.__preset_select = document.createElement('select');
2613 |
2614 | if (gui.load && gui.load.remembered) {
2615 |
2616 | common.each(gui.load.remembered, function(value, key) {
2617 | addPresetOption(gui, key, key == gui.preset);
2618 | });
2619 |
2620 | } else {
2621 | addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false);
2622 | }
2623 |
2624 | dom.bind(select, 'change', function() {
2625 |
2626 |
2627 | for (var index = 0; index < gui.__preset_select.length; index++) {
2628 | gui.__preset_select[index].innerHTML = gui.__preset_select[index].value;
2629 | }
2630 |
2631 | gui.preset = this.value;
2632 |
2633 | });
2634 |
2635 | div.appendChild(select);
2636 | div.appendChild(gears);
2637 | div.appendChild(button);
2638 | div.appendChild(button2);
2639 | div.appendChild(button3);
2640 |
2641 | if (SUPPORTS_LOCAL_STORAGE) {
2642 |
2643 | var saveLocally = document.getElementById('dg-save-locally');
2644 | var explain = document.getElementById('dg-local-explain');
2645 |
2646 | saveLocally.style.display = 'block';
2647 |
2648 | var localStorageCheckBox = document.getElementById('dg-local-storage');
2649 |
2650 | if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') {
2651 | localStorageCheckBox.setAttribute('checked', 'checked');
2652 | }
2653 |
2654 | function showHideExplain() {
2655 | explain.style.display = gui.useLocalStorage ? 'block' : 'none';
2656 | }
2657 |
2658 | showHideExplain();
2659 |
2660 | // TODO: Use a boolean controller, fool!
2661 | dom.bind(localStorageCheckBox, 'change', function() {
2662 | gui.useLocalStorage = !gui.useLocalStorage;
2663 | showHideExplain();
2664 | });
2665 |
2666 | }
2667 |
2668 | var newConstructorTextArea = document.getElementById('dg-new-constructor');
2669 |
2670 | dom.bind(newConstructorTextArea, 'keydown', function(e) {
2671 | if (e.metaKey && (e.which === 67 || e.keyCode == 67)) {
2672 | SAVE_DIALOGUE.hide();
2673 | }
2674 | });
2675 |
2676 | dom.bind(gears, 'click', function() {
2677 | newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2);
2678 | SAVE_DIALOGUE.show();
2679 | newConstructorTextArea.focus();
2680 | newConstructorTextArea.select();
2681 | });
2682 |
2683 | dom.bind(button, 'click', function() {
2684 | gui.save();
2685 | });
2686 |
2687 | dom.bind(button2, 'click', function() {
2688 | var presetName = prompt('Enter a new preset name.');
2689 | if (presetName) gui.saveAs(presetName);
2690 | });
2691 |
2692 | dom.bind(button3, 'click', function() {
2693 | gui.revert();
2694 | });
2695 |
2696 | // div.appendChild(button2);
2697 |
2698 | }
2699 |
2700 | function addResizeHandle(gui) {
2701 |
2702 | gui.__resize_handle = document.createElement('div');
2703 |
2704 | common.extend(gui.__resize_handle.style, {
2705 |
2706 | width: '6px',
2707 | marginLeft: '-3px',
2708 | height: '200px',
2709 | cursor: 'ew-resize',
2710 | position: 'absolute'
2711 | // border: '1px solid blue'
2712 |
2713 | });
2714 |
2715 | var pmouseX;
2716 |
2717 | dom.bind(gui.__resize_handle, 'mousedown', dragStart);
2718 | dom.bind(gui.__closeButton, 'mousedown', dragStart);
2719 |
2720 | gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild);
2721 |
2722 | function dragStart(e) {
2723 |
2724 | e.preventDefault();
2725 |
2726 | pmouseX = e.clientX;
2727 |
2728 | dom.addClass(gui.__closeButton, GUI.CLASS_DRAG);
2729 | dom.bind(window, 'mousemove', drag);
2730 | dom.bind(window, 'mouseup', dragStop);
2731 |
2732 | return false;
2733 |
2734 | }
2735 |
2736 | function drag(e) {
2737 |
2738 | e.preventDefault();
2739 |
2740 | gui.width += pmouseX - e.clientX;
2741 | gui.onResize();
2742 | pmouseX = e.clientX;
2743 |
2744 | return false;
2745 |
2746 | }
2747 |
2748 | function dragStop() {
2749 |
2750 | dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG);
2751 | dom.unbind(window, 'mousemove', drag);
2752 | dom.unbind(window, 'mouseup', dragStop);
2753 |
2754 | }
2755 |
2756 | }
2757 |
2758 | function setWidth(gui, w) {
2759 | gui.domElement.style.width = w + 'px';
2760 | // Auto placed save-rows are position fixed, so we have to
2761 | // set the width manually if we want it to bleed to the edge
2762 | if (gui.__save_row && gui.autoPlace) {
2763 | gui.__save_row.style.width = w + 'px';
2764 | }if (gui.__closeButton) {
2765 | gui.__closeButton.style.width = w + 'px';
2766 | }
2767 | }
2768 |
2769 | function getCurrentPreset(gui, useInitialValues) {
2770 |
2771 | var toReturn = {};
2772 |
2773 | // For each object I'm remembering
2774 | common.each(gui.__rememberedObjects, function(val, index) {
2775 |
2776 | var saved_values = {};
2777 |
2778 | // The controllers I've made for thcommon.isObject by property
2779 | var controller_map =
2780 | gui.__rememberedObjectIndecesToControllers[index];
2781 |
2782 | // Remember each value for each property
2783 | common.each(controller_map, function(controller, property) {
2784 | saved_values[property] = useInitialValues ? controller.initialValue : controller.getValue();
2785 | });
2786 |
2787 | // Save the values for thcommon.isObject
2788 | toReturn[index] = saved_values;
2789 |
2790 | });
2791 |
2792 | return toReturn;
2793 |
2794 | }
2795 |
2796 | function addPresetOption(gui, name, setSelected) {
2797 | var opt = document.createElement('option');
2798 | opt.innerHTML = name;
2799 | opt.value = name;
2800 | gui.__preset_select.appendChild(opt);
2801 | if (setSelected) {
2802 | gui.__preset_select.selectedIndex = gui.__preset_select.length - 1;
2803 | }
2804 | }
2805 |
2806 | function setPresetSelectIndex(gui) {
2807 | for (var index = 0; index < gui.__preset_select.length; index++) {
2808 | if (gui.__preset_select[index].value == gui.preset) {
2809 | gui.__preset_select.selectedIndex = index;
2810 | }
2811 | }
2812 | }
2813 |
2814 | function markPresetModified(gui, modified) {
2815 | var opt = gui.__preset_select[gui.__preset_select.selectedIndex];
2816 | // console.log('mark', modified, opt);
2817 | if (modified) {
2818 | opt.innerHTML = opt.value + "*";
2819 | } else {
2820 | opt.innerHTML = opt.value;
2821 | }
2822 | }
2823 |
2824 | function updateDisplays(controllerArray) {
2825 |
2826 |
2827 | if (controllerArray.length != 0) {
2828 |
2829 | requestAnimationFrame(function() {
2830 | updateDisplays(controllerArray);
2831 | });
2832 |
2833 | }
2834 |
2835 | common.each(controllerArray, function(c) {
2836 | c.updateDisplay();
2837 | });
2838 |
2839 | }
2840 |
2841 | return GUI;
2842 |
2843 | })(dat.utils.css,
2844 | "
GUI
's constructor:\n\n \n\n localStorage
on exit.\n\n 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