gui.full.js 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. var GUI = function() {
  2. var _this = this;
  3. var MIN_WIDTH = 240;
  4. var MAX_WIDTH = 500;
  5. var controllers = [];
  6. var listening = [];
  7. var autoListen = true;
  8. var listenInterval;
  9. // Sum total of heights of controllers in this gui
  10. var controllerHeight;
  11. var curControllerContainerHeight = 0;
  12. var _this = this;
  13. var open = false;
  14. var width = 280;
  15. // Prevents checkForOverflow bug in which loaded gui appearance
  16. // settings are not respected by presence of scrollbar.
  17. var explicitOpenHeight = false;
  18. // How big we get when we open
  19. var openHeight;
  20. var name;
  21. var resizeTo = 0;
  22. var resizeTimeout;
  23. this.domElement = document.createElement('div');
  24. this.domElement.setAttribute('class', 'guidat');
  25. this.domElement.style.width = width+'px';
  26. var controllerContainer = document.createElement('div');
  27. controllerContainer.setAttribute('class', 'guidat-controllers');
  28. // Firefox hack to prevent horizontal scrolling
  29. controllerContainer.addEventListener('DOMMouseScroll', function(e) {
  30. var scrollAmount = this.scrollTop;
  31. if (e.wheelDelta) {
  32. scrollAmount+=e.wheelDelta;
  33. } else if (e.detail) {
  34. scrollAmount+=e.detail;
  35. }
  36. if (e.preventDefault) {
  37. e.preventDefault();
  38. }
  39. e.returnValue = false;
  40. controllerContainer.scrollTop = scrollAmount;
  41. }, false);
  42. controllerContainer.style.height = '0px';
  43. var toggleButton = document.createElement('a');
  44. toggleButton.setAttribute('class', 'guidat-toggle');
  45. toggleButton.setAttribute('href', '#');
  46. toggleButton.innerHTML = "Show Controls";
  47. var toggleDragged = false;
  48. var dragDisplacementY = 0;
  49. var togglePressed = false;
  50. var my, pmy, mx, pmx;
  51. var resize = function(e) {
  52. pmy = my;
  53. pmx = mx;
  54. my = e.pageY;
  55. mx = e.pageX;
  56. var dmy = my - pmy;
  57. if (!open) {
  58. if (dmy > 0) {
  59. open = true;
  60. curControllerContainerHeight = openHeight = 1;
  61. toggleButton.innerHTML = name || "Hide Controls";
  62. } else {
  63. return;
  64. }
  65. }
  66. // TODO: Flip this if you want to resize to the left.
  67. var dmx = pmx - mx;
  68. if (dmy > 0 &&
  69. curControllerContainerHeight > controllerHeight) {
  70. var d = GUI.map(curControllerContainerHeight, controllerHeight, controllerHeight + 100, 1, 0);
  71. dmy *= d;
  72. }
  73. toggleDragged = true;
  74. dragDisplacementY += dmy;
  75. dragDisplacementX += dmx;
  76. openHeight += dmy;
  77. width += dmx;
  78. curControllerContainerHeight += dmy;
  79. controllerContainer.style.height = openHeight+'px';
  80. width = GUI.constrain(width, MIN_WIDTH, MAX_WIDTH);
  81. _this.domElement.style.width = width+'px';
  82. checkForOverflow();
  83. };
  84. toggleButton.addEventListener('mousedown', function(e) {
  85. pmy = my = e.pageY;
  86. pmx = mx = e.pageX;
  87. togglePressed = true;
  88. e.preventDefault();
  89. dragDisplacementY = 0;
  90. dragDisplacementX = 0;
  91. document.addEventListener('mousemove', resize, false);
  92. return false;
  93. }, false);
  94. toggleButton.addEventListener('click', function(e) {
  95. e.preventDefault();
  96. return false;
  97. }, false);
  98. document.addEventListener('mouseup', function(e) {
  99. if (togglePressed && !toggleDragged) {
  100. _this.toggle();
  101. }
  102. if (togglePressed && toggleDragged) {
  103. if (dragDisplacementX == 0) {
  104. adaptToScrollbar();
  105. }
  106. if (openHeight > controllerHeight) {
  107. clearTimeout(resizeTimeout);
  108. openHeight = resizeTo = controllerHeight;
  109. beginResize();
  110. } else if (controllerContainer.children.length >= 1) {
  111. var singleControllerHeight = controllerContainer.children[0].offsetHeight;
  112. clearTimeout(resizeTimeout);
  113. var target = Math.round(curControllerContainerHeight/singleControllerHeight)*singleControllerHeight-1;
  114. resizeTo = target;
  115. if (resizeTo <= 0) {
  116. _this.hide();
  117. openHeight = singleControllerHeight*2;
  118. } else {
  119. openHeight = resizeTo;
  120. beginResize();
  121. }
  122. }
  123. };
  124. document.removeEventListener('mousemove', resize, false);
  125. e.preventDefault();
  126. toggleDragged = false;
  127. togglePressed = false;
  128. return false;
  129. }, false);
  130. this.domElement.appendChild(controllerContainer);
  131. this.domElement.appendChild(toggleButton);
  132. if (GUI.autoPlace) {
  133. if(GUI.autoPlaceContainer == null) {
  134. GUI.autoPlaceContainer = document.createElement('div');
  135. GUI.autoPlaceContainer.setAttribute("id", "guidat");
  136. document.body.appendChild(GUI.autoPlaceContainer);
  137. }
  138. GUI.autoPlaceContainer.appendChild(this.domElement);
  139. }
  140. this.autoListenIntervalTime = 1000/60;
  141. var createListenInterval = function() {
  142. listenInterval = setInterval(function() {
  143. _this.listen();
  144. }, this.autoListenIntervalTime);
  145. };
  146. this.__defineSetter__("autoListen", function(v) {
  147. autoListen = v;
  148. if (!autoListen) {
  149. clearInterval(listenInterval);
  150. } else {
  151. if (listening.length > 0) createListenInterval();
  152. }
  153. });
  154. this.__defineGetter__("autoListen", function(v) {
  155. return autoListen;
  156. });
  157. this.listenTo = function(controller) {
  158. // TODO: check for duplicates
  159. if (listening.length == 0) {
  160. createListenInterval();
  161. }
  162. listening.push(controller);
  163. };
  164. this.unlistenTo = function(controller) {
  165. // TODO: test this
  166. for(var i = 0; i < listening.length; i++) {
  167. if(listening[i] == controller) listening.splice(i, 1);
  168. }
  169. if(listening.length <= 0) {
  170. clearInterval(listenInterval);
  171. }
  172. };
  173. this.listen = function(whoToListenTo) {
  174. var arr = whoToListenTo || listening;
  175. for (var i in arr) {
  176. arr[i].updateDisplay();
  177. }
  178. };
  179. this.listenAll = function() {
  180. this.listen(controllers);
  181. }
  182. this.autoListen = true;
  183. var alreadyControlled = function(object, propertyName) {
  184. for (var i in controllers) {
  185. if (controllers[i].object == object &&
  186. controllers[i].propertyName == propertyName) {
  187. return true;
  188. }
  189. }
  190. return false;
  191. };
  192. var construct = function(constructor, args) {
  193. function F() {
  194. return constructor.apply(this, args);
  195. }
  196. F.prototype = constructor.prototype;
  197. return new F();
  198. };
  199. this.add = function() {
  200. var object = arguments[0];
  201. var propertyName = arguments[1];
  202. // Have we already added this?
  203. if (alreadyControlled(object, propertyName)) {
  204. // GUI.error("Controller for \"" + propertyName+"\" already added.");
  205. // return;
  206. }
  207. var value = object[propertyName];
  208. // Does this value exist? Is it accessible?
  209. if (value == undefined) {
  210. GUI.error(object + " either has no property \""+propertyName+"\", or the property is inaccessible.");
  211. return;
  212. }
  213. var type = typeof value;
  214. var handler = handlerTypes[type];
  215. // Do we know how to deal with this data type?
  216. if (handler == undefined) {
  217. GUI.error("Cannot create controller for data type \""+type+"\"");
  218. return;
  219. }
  220. var args = [this]; // Set first arg (parent) to this
  221. for (var j = 0; j < arguments.length; j++) {
  222. args.push(arguments[j]);
  223. }
  224. var controllerObject = construct(handler, args);
  225. // Were we able to make the controller?
  226. if (!controllerObject) {
  227. GUI.error("Error creating controller for \""+propertyName+"\".");
  228. return;
  229. }
  230. // Success.
  231. controllerContainer.appendChild(controllerObject.domElement);
  232. controllers.push(controllerObject);
  233. GUI.allControllers.push(controllerObject);
  234. // Do we have a saved value for this controller?
  235. if (type != "function" &&
  236. GUI.saveIndex < GUI.savedValues.length) {
  237. controllerObject.setValue(GUI.savedValues[GUI.saveIndex]);
  238. GUI.saveIndex++;
  239. }
  240. // Compute sum height of controllers.
  241. checkForOverflow();
  242. // Prevents checkForOverflow bug in which loaded gui appearance
  243. // settings are not respected by presence of scrollbar.
  244. if (!explicitOpenHeight) {
  245. openHeight = controllerHeight;
  246. }
  247. return controllerObject;
  248. }
  249. var checkForOverflow = function() {
  250. controllerHeight = 0;
  251. for (var i in controllers) {
  252. controllerHeight += controllers[i].domElement.offsetHeight;
  253. }
  254. if (controllerHeight - 1 > openHeight) {
  255. controllerContainer.style.overflowY = "auto";
  256. } else {
  257. controllerContainer.style.overflowY = "hidden";
  258. }
  259. };
  260. var handlerTypes = {
  261. "number": GUI.NumberController,
  262. "string": GUI.StringController,
  263. "boolean": GUI.BooleanController,
  264. "function": GUI.FunctionController
  265. };
  266. var alreadyControlled = function(object, propertyName) {
  267. for (var i in controllers) {
  268. if (controllers[i].object == object &&
  269. controllers[i].propertyName == propertyName) {
  270. return true;
  271. }
  272. }
  273. return false;
  274. };
  275. var construct = function(constructor, args) {
  276. function F() {
  277. return constructor.apply(this, args);
  278. }
  279. F.prototype = constructor.prototype;
  280. return new F();
  281. };
  282. this.reset = function() {
  283. // TODO
  284. }
  285. // GUI ... GUI
  286. this.toggle = function() {
  287. open ? this.hide() : this.show();
  288. };
  289. this.show = function() {
  290. toggleButton.innerHTML = name || "Hide Controls";
  291. resizeTo = openHeight;
  292. clearTimeout(resizeTimeout);
  293. beginResize();
  294. open = true;
  295. }
  296. this.hide = function() {
  297. toggleButton.innerHTML = name || "Show Controls";
  298. resizeTo = 0;
  299. clearTimeout(resizeTimeout);
  300. beginResize();
  301. open = false;
  302. }
  303. this.name = function(n) {
  304. name = n;
  305. toggleButton.innerHTML = n;
  306. }
  307. // used in saveURL
  308. this.appearanceVars = function() {
  309. return [open, width, openHeight, controllerContainer.scrollTop]
  310. }
  311. var beginResize = function() {
  312. //console.log("Resizing from " + curControllerContainerHeight + " to " + resizeTo);
  313. curControllerContainerHeight += (resizeTo - curControllerContainerHeight)*0.6;
  314. if (Math.abs(curControllerContainerHeight-resizeTo) < 1) {
  315. curControllerContainerHeight = resizeTo;
  316. adaptToScrollbar();
  317. } else {
  318. resizeTimeout = setTimeout(beginResize, 1000/30);
  319. }
  320. controllerContainer.style.height = Math.round(curControllerContainerHeight)+'px';
  321. checkForOverflow();
  322. }
  323. var adaptToScrollbar = function() {
  324. // Clears lingering slider column
  325. _this.domElement.style.width = (width+1)+'px';
  326. setTimeout(function() {
  327. _this.domElement.style.width = width+'px';
  328. }, 1);
  329. };
  330. // Load saved appearance:
  331. if (GUI.guiIndex < GUI.savedAppearanceVars.length) {
  332. width = parseInt(GUI.savedAppearanceVars[GUI.guiIndex][1]);
  333. _this.domElement.style.width = width+"px";
  334. openHeight = parseInt(GUI.savedAppearanceVars[GUI.guiIndex][2]);
  335. explicitOpenHeight = true;
  336. if (eval(GUI.savedAppearanceVars[GUI.guiIndex][0]) == true) {
  337. curControllerContainerHeight = openHeight;
  338. var t = GUI.savedAppearanceVars[GUI.guiIndex][3]
  339. // Hack.
  340. setTimeout(function() {
  341. controllerContainer.scrollTop = t;
  342. }, 0);
  343. if (GUI.scrollTop > -1) {
  344. document.body.scrollTop = GUI.scrollTop;
  345. }
  346. resizeTo = openHeight;
  347. this.show();
  348. }
  349. GUI.guiIndex++;
  350. }
  351. GUI.allGuis.push(this);
  352. // Add hide listener if this is the first GUI.
  353. if (GUI.allGuis.length == 1) {
  354. window.addEventListener('keyup', function(e) {
  355. // Hide on "H"
  356. if (e.keyCode == 72) {
  357. GUI.toggleHide();
  358. }
  359. }, false);
  360. }
  361. };
  362. // Do not set this directly.
  363. GUI.hidden = false;
  364. // Static members
  365. GUI.autoPlace = true;
  366. GUI.autoPlaceContainer = null;
  367. GUI.allControllers = [];
  368. GUI.allGuis = [];
  369. GUI.toggleHide = function() {
  370. if (GUI.hidden) {
  371. GUI.show();
  372. } else {
  373. GUI.hide();
  374. }
  375. }
  376. GUI.show = function() {
  377. GUI.hidden = false;
  378. for (var i in GUI.allGuis) {
  379. GUI.allGuis[i].domElement.style.display = "block";
  380. }
  381. }
  382. GUI.hide = function() {
  383. GUI.hidden = true;
  384. for (var i in GUI.allGuis) {
  385. GUI.allGuis[i].domElement.style.display = "none";
  386. }
  387. }
  388. GUI.saveURL = function() {
  389. var url = GUI.replaceGetVar("saveString", GUI.getSaveString());
  390. window.location = url;
  391. };
  392. GUI.scrollTop = -1;
  393. GUI.load = function(saveString) {
  394. //GUI.savedAppearanceVars = [];
  395. var vals = saveString.split(",");
  396. var numGuis = parseInt(vals[0]);
  397. GUI.scrollTop = parseInt(vals[1]);
  398. for (var i = 0; i < numGuis; i++) {
  399. var appr = vals.splice(2, 4);
  400. GUI.savedAppearanceVars.push(appr);
  401. }
  402. GUI.savedValues = vals.splice(2, vals.length);
  403. };
  404. GUI.savedValues = [];
  405. GUI.savedAppearanceVars = [];
  406. GUI.getSaveString = function() {
  407. var vals = [],
  408. i;
  409. vals.push(GUI.allGuis.length);
  410. vals.push(document.body.scrollTop);
  411. for (i in GUI.allGuis) {
  412. var av = GUI.allGuis[i].appearanceVars();
  413. for (var j = 0; j < av.length; j++) {
  414. vals.push(av[j]);
  415. }
  416. }
  417. for (i in GUI.allControllers) {
  418. // We don't save values for functions.
  419. if (GUI.allControllers[i].type == "function") {
  420. continue;
  421. }
  422. var v = GUI.allControllers[i].getValue();
  423. // Round numbers so they don't get enormous
  424. if (GUI.allControllers[i].type == "number") {
  425. v = GUI.roundToDecimal(v, 4);
  426. }
  427. vals.push(v);
  428. }
  429. return vals.join(',');
  430. };
  431. GUI.getVarFromURL = function(v) {
  432. var vars = [], hash;
  433. var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  434. for (var i = 0; i < hashes.length; i++) {
  435. hash = hashes[i].split("=");
  436. if (hash == undefined) continue;
  437. if (hash[0] == v) {
  438. return hash[1];
  439. }
  440. }
  441. return null;
  442. };
  443. GUI.replaceGetVar = function(varName, val) {
  444. var vars = [], hash;
  445. var loc = window.location.href;
  446. var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
  447. for (var i = 0; i < hashes.length; i++) {
  448. hash = hashes[i].split("=");
  449. if (hash == undefined) continue;
  450. if (hash[0] == varName) {
  451. return loc.replace(hash[1], val);
  452. }
  453. }
  454. if (window.location.href.indexOf('?') != -1) {
  455. return loc + "&"+varName+"="+val;
  456. }
  457. return loc+"?"+varName+"="+val;
  458. };
  459. GUI.saveIndex = 0;
  460. GUI.guiIndex = 0;
  461. GUI.showSaveString = function() {
  462. alert(GUI.getSaveString());
  463. };
  464. // Util functions
  465. GUI.makeUnselectable = function(elem) {
  466. elem.onselectstart = function() { return false; };
  467. elem.style.MozUserSelect = "none";
  468. elem.style.KhtmlUserSelect = "none";
  469. elem.unselectable = "on";
  470. };
  471. GUI.makeSelectable = function(elem) {
  472. elem.onselectstart = function() { };
  473. elem.style.MozUserSelect = "auto";
  474. elem.style.KhtmlUserSelect = "auto";
  475. elem.unselectable = "off";
  476. };
  477. GUI.map = function(v, i1, i2, o1, o2) {
  478. return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
  479. };
  480. GUI.constrain = function (v, o1, o2) {
  481. if (v < o1) v = o1;
  482. else if (v > o2) v = o2;
  483. return v;
  484. };
  485. GUI.error = function(str) {
  486. if (typeof console.error == 'function') {
  487. console.error("[GUI ERROR] " + str);
  488. }
  489. };
  490. GUI.roundToDecimal = function(n, decimals) {
  491. var t = Math.pow(10, decimals);
  492. return Math.round(n*t)/t;
  493. };
  494. GUI.extendController = function(clazz) {
  495. clazz.prototype = new GUI.Controller();
  496. clazz.prototype.constructor = clazz;
  497. };
  498. if (GUI.getVarFromURL('saveString') != null) GUI.load(GUI.getVarFromURL('saveString'));
  499. GUI.Slider = function(numberController, min, max, step, initValue) {
  500. var clicked = false;
  501. var _this = this;
  502. var x, px;
  503. this.domElement = document.createElement('div');
  504. this.domElement.setAttribute('class', 'guidat-slider-bg');
  505. this.fg = document.createElement('div');
  506. this.fg.setAttribute('class', 'guidat-slider-fg');
  507. this.domElement.appendChild(this.fg);
  508. var onDrag = function(e) {
  509. if (!clicked) return;
  510. var pos = findPos(_this.domElement);
  511. var val = GUI.map(e.pageX, pos[0], pos[0] + _this.domElement.offsetWidth, min, max);
  512. val = Math.round(val/step)*step;
  513. numberController.setValue(val);
  514. };
  515. this.domElement.addEventListener('mousedown', function(e) {
  516. clicked = true;
  517. x = px = e.pageX;
  518. _this.domElement.className += ' active';
  519. _this.fg.className += ' active';
  520. numberController.domElement.className += ' active';
  521. onDrag(e);
  522. document.addEventListener('mouseup', mouseup, false);
  523. }, false);
  524. var mouseup = function(e) {
  525. _this.domElement.className = _this.domElement.className.replace(' active', '');
  526. _this.fg.className = _this.fg.className.replace(' active', '');
  527. numberController.domElement.className = numberController.domElement.className.replace(' active', '');
  528. clicked = false;
  529. if (numberController.finishChangeFunction != null) {
  530. numberController.finishChangeFunction.call(this, numberController.getValue());
  531. }
  532. document.removeEventListener('mouseup', mouseup, false);
  533. };
  534. var findPos = function(obj) {
  535. var curleft = 0, curtop = 0;
  536. if (obj.offsetParent) {
  537. do {
  538. curleft += obj.offsetLeft;
  539. curtop += obj.offsetTop;
  540. } while ((obj = obj.offsetParent));
  541. return [curleft,curtop];
  542. }
  543. };
  544. this.__defineSetter__('value', function(e) {
  545. var pct = GUI.map(e, min, max, 0, 100);
  546. this.fg.style.width = pct+"%";
  547. });
  548. var onDrag = function(e) {
  549. if (!clicked) return;
  550. var pos = findPos(_this.domElement);
  551. var val = GUI.map(e.pageX, pos[0], pos[0] + _this.domElement.offsetWidth, min, max);
  552. val = Math.round(val/step)*step;
  553. numberController.setValue(val);
  554. };
  555. this.domElement.addEventListener('mousedown', function(e) {
  556. clicked = true;
  557. x = px = e.pageX;
  558. _this.domElement.setAttribute('class', 'guidat-slider-bg active');
  559. _this.fg.setAttribute('class', 'guidat-slider-fg active');
  560. onDrag(e);
  561. document.addEventListener('mouseup', mouseup, false);
  562. }, false);
  563. var mouseup = function(e) {
  564. _this.domElement.setAttribute('class', 'guidat-slider-bg');
  565. _this.fg.setAttribute('class', 'guidat-slider-fg');
  566. clicked = false;
  567. if (numberController.finishChangeFunction != null) {
  568. numberController.finishChangeFunction.call(this, numberController.getValue());
  569. }
  570. document.removeEventListener('mouseup', mouseup, false);
  571. };
  572. document.addEventListener('mousemove', onDrag, false);
  573. this.value = initValue;
  574. };
  575. GUI.Controller = function() {
  576. this.parent = arguments[0];
  577. this.object = arguments[1];
  578. this.propertyName = arguments[2];
  579. if (arguments.length > 0) this.initialValue = this.propertyName[this.object];
  580. this.domElement = document.createElement('div');
  581. this.domElement.setAttribute('class', 'guidat-controller ' + this.type);
  582. this.propertyNameElement = document.createElement('span');
  583. this.propertyNameElement.setAttribute('class', 'guidat-propertyname');
  584. this.name(this.propertyName);
  585. this.domElement.appendChild(this.propertyNameElement);
  586. GUI.makeUnselectable(this.domElement);
  587. };
  588. GUI.Controller.prototype.changeFunction = null;
  589. GUI.Controller.prototype.finishChangeFunction = null;
  590. GUI.Controller.prototype.name = function(n) {
  591. this.propertyNameElement.innerHTML = n;
  592. return this;
  593. };
  594. GUI.Controller.prototype.reset = function() {
  595. this.setValue(this.initialValue);
  596. return this;
  597. };
  598. GUI.Controller.prototype.listen = function() {
  599. this.parent.listenTo(this);
  600. return this;
  601. };
  602. GUI.Controller.prototype.unlisten = function() {
  603. this.parent.unlistenTo(this); // <--- hasn't been tested yet
  604. return this;
  605. };
  606. GUI.Controller.prototype.setValue = function(n) {
  607. this.object[this.propertyName] = n;
  608. if (this.changeFunction != null) {
  609. this.changeFunction.call(this, n);
  610. }
  611. this.updateDisplay();
  612. return this;
  613. };
  614. GUI.Controller.prototype.getValue = function() {
  615. return this.object[this.propertyName];
  616. };
  617. GUI.Controller.prototype.updateDisplay = function() {};
  618. GUI.Controller.prototype.onChange = function(fnc) {
  619. this.changeFunction = fnc;
  620. return this;
  621. };
  622. GUI.Controller.prototype.onFinishChange = function(fnc) {
  623. this.finishChangeFunction = fnc;
  624. return this;
  625. };
  626. GUI.Controller.prototype.options = function() {
  627. var _this = this;
  628. var select = document.createElement('select');
  629. if (arguments.length == 1) {
  630. var arr = arguments[0];
  631. for (var i in arr) {
  632. var opt = document.createElement('option');
  633. opt.innerHTML = i;
  634. opt.setAttribute('value', arr[i]);
  635. select.appendChild(opt);
  636. }
  637. } else {
  638. for (var i = 0; i < arguments.length; i++) {
  639. var opt = document.createElement('option');
  640. opt.innerHTML = arguments[i];
  641. opt.setAttribute('value', arguments[i]);
  642. select.appendChild(opt);
  643. }
  644. }
  645. select.addEventListener('change', function() {
  646. _this.setValue(this.value);
  647. if (_this.finishChangeFunction != null) {
  648. _this.finishChangeFunction.call(this, _this.getValue());
  649. }
  650. }, false);
  651. _this.domElement.appendChild(select);
  652. return this;
  653. };
  654. GUI.BooleanController = function() {
  655. this.type = "boolean";
  656. GUI.Controller.apply(this, arguments);
  657. var _this = this;
  658. var input = document.createElement('input');
  659. input.setAttribute('type', 'checkbox');
  660. this.domElement.addEventListener('click', function(e) {
  661. input.checked = !input.checked;
  662. e.preventDefault();
  663. _this.setValue(input.checked);
  664. }, false);
  665. input.addEventListener('mouseup', function(e) {
  666. input.checked = !input.checked; // counteracts default.
  667. }, false);
  668. this.domElement.style.cursor = "pointer";
  669. this.propertyNameElement.style.cursor = "pointer";
  670. this.domElement.appendChild(input);
  671. this.updateDisplay = function() {
  672. input.checked = _this.getValue();
  673. };
  674. this.setValue = function(val) {
  675. if (typeof val != "boolean") {
  676. try {
  677. val = eval(val);
  678. } catch (e) {}
  679. }
  680. return GUI.Controller.prototype.setValue.call(this, val);
  681. };
  682. };
  683. GUI.extendController(GUI.BooleanController);
  684. GUI.FunctionController = function() {
  685. this.type = "function";
  686. var _this = this;
  687. GUI.Controller.apply(this, arguments);
  688. this.domElement.addEventListener('click', function() {
  689. _this.fire();
  690. }, false);
  691. this.domElement.style.cursor = "pointer";
  692. this.propertyNameElement.style.cursor = "pointer";
  693. var fireFunction = null;
  694. this.onFire = function(fnc) {
  695. fireFunction = fnc;
  696. return this;
  697. }
  698. this.fire = function() {
  699. if (fireFunction != null) {
  700. fireFunction.call(this);
  701. }
  702. _this.object[_this.propertyName].call(_this.object);
  703. };
  704. };
  705. GUI.extendController(GUI.FunctionController);
  706. GUI.NumberController = function() {
  707. this.type = "number";
  708. GUI.Controller.apply(this, arguments);
  709. var _this = this;
  710. // If we simply click and release a number field, we want to highlight it.
  711. // This variable keeps track of whether or not we've dragged
  712. var draggedNumberField = false;
  713. var clickedNumberField = false;
  714. var y = 0, py = 0;
  715. var min = arguments[3];
  716. var max = arguments[4];
  717. var step = arguments[5];
  718. if (!step) {
  719. if (min != undefined && max != undefined) {
  720. step = (max-min)*0.01;
  721. } else {
  722. step = 1;
  723. }
  724. }
  725. var numberField = document.createElement('input');
  726. numberField.setAttribute('id', this.propertyName);
  727. numberField.setAttribute('type', 'text');
  728. numberField.setAttribute('value', this.getValue());
  729. if (step) numberField.setAttribute('step', step);
  730. this.domElement.appendChild(numberField);
  731. var slider;
  732. if (min != undefined && max != undefined) {
  733. slider = new GUI.Slider(this, min, max, step, this.getValue());
  734. this.domElement.appendChild(slider.domElement);
  735. }
  736. numberField.addEventListener('blur', function(e) {
  737. var val = parseFloat(this.value);
  738. if (!isNaN(val)) {
  739. _this.setValue(val);
  740. }
  741. }, false);
  742. numberField.addEventListener('mousewheel', function(e) {
  743. e.preventDefault();
  744. _this.setValue(_this.getValue() + Math.abs(e.wheelDeltaY)/e.wheelDeltaY*step);
  745. return false;
  746. }, false);
  747. numberField.addEventListener('mousedown', function(e) {
  748. py = y = e.pageY;
  749. clickedNumberField = true;
  750. document.addEventListener('mousemove', dragNumberField, false);
  751. document.addEventListener('mouseup', mouseup, false);
  752. }, false);
  753. // Handle up arrow and down arrow
  754. numberField.addEventListener('keydown', function(e) {
  755. var newVal;
  756. switch(e.keyCode) {
  757. case 13: // enter
  758. newVal = parseFloat(this.value);
  759. _this.setValue(newVal);
  760. break;
  761. case 38: // up
  762. newVal = _this.getValue() + step;
  763. _this.setValue(newVal);
  764. break;
  765. case 40: // down
  766. newVal = _this.getValue() - step;
  767. _this.setValue(newVal);
  768. break;
  769. }
  770. }, false);
  771. var mouseup = function(e) {
  772. document.removeEventListener('mousemove', dragNumberField, false);
  773. GUI.makeSelectable(_this.parent.domElement);
  774. GUI.makeSelectable(numberField);
  775. if (clickedNumberField && !draggedNumberField) {
  776. numberField.focus();
  777. numberField.select();
  778. }
  779. if(slider) slider.domElement.className = slider.domElement.className.replace(' active', '');
  780. draggedNumberField = false;
  781. clickedNumberField = false;
  782. if (_this.finishChangeFunction != null) {
  783. _this.finishChangeFunction.call(this, _this.getValue());
  784. }
  785. document.removeEventListener('mouseup', mouseup, false);
  786. };
  787. var dragNumberField = function(e) {
  788. draggedNumberField = true;
  789. e.preventDefault();
  790. // We don't want to be highlighting this field as we scroll.
  791. // Or any other fields in this gui for that matter ...
  792. // TODO: Make makeUselectable go through each element and child element.
  793. GUI.makeUnselectable(_this.parent.domElement);
  794. GUI.makeUnselectable(numberField);
  795. if(slider) slider.domElement.className += ' active';
  796. py = y;
  797. y = e.pageY;
  798. var dy = py - y;
  799. var newVal = _this.getValue() + dy*step;
  800. _this.setValue(newVal);
  801. return false;
  802. };
  803. this.options = function() {
  804. _this.noSlider();
  805. _this.domElement.removeChild(numberField);
  806. return GUI.Controller.prototype.options.apply(this, arguments);
  807. };
  808. this.noSlider = function() {
  809. if (slider) {
  810. _this.domElement.removeChild(slider.domElement);
  811. }
  812. return this;
  813. };
  814. this.setValue = function(val) {
  815. val = parseFloat(val);
  816. if (min != undefined && val <= min) {
  817. val = min;
  818. } else if (max != undefined && val >= max) {
  819. val = max;
  820. }
  821. return GUI.Controller.prototype.setValue.call(this, val);
  822. };
  823. this.updateDisplay = function() {
  824. numberField.value = GUI.roundToDecimal(_this.getValue(), 4);
  825. if (slider) slider.value = _this.getValue();
  826. };
  827. };
  828. GUI.extendController(GUI.NumberController);
  829. GUI.StringController = function() {
  830. this.type = "string";
  831. var _this = this;
  832. GUI.Controller.apply(this, arguments);
  833. var input = document.createElement('input');
  834. var initialValue = this.getValue();
  835. input.setAttribute('value', initialValue);
  836. input.setAttribute('spellcheck', 'false');
  837. this.domElement.addEventListener('mouseup', function() {
  838. input.focus();
  839. input.select();
  840. }, false);
  841. // TODO: getting messed up on ctrl a
  842. input.addEventListener('keyup', function(e) {
  843. if (e.keyCode == 13 && _this.finishChangeFunction != null) {
  844. _this.finishChangeFunction.call(this, _this.getValue());
  845. }
  846. _this.setValue(input.value);
  847. }, false);
  848. input.addEventListener('blur', function() {
  849. if (_this.finishChangeFunction != null) {
  850. _this.finishChangeFunction.call(this, _this.getValue());
  851. }
  852. }, false);
  853. this.updateDisplay = function() {
  854. input.value = _this.getValue();
  855. };
  856. this.options = function() {
  857. _this.domElement.removeChild(input);
  858. return GUI.Controller.prototype.options.apply(this, arguments);
  859. };
  860. this.domElement.appendChild(input);
  861. };
  862. GUI.extendController(GUI.StringController);