From 554767fa70031de6266794b495cf922cbf5cd8c0 Mon Sep 17 00:00:00 2001 From: George Michael Brower Date: Sun, 30 Jan 2011 16:03:09 -0700 Subject: [PATCH] totally resizeable :) --- controllers/controller.number.js | 6 +- demo/demo.js | 18 ++- gui.css | 33 ++-- gui.js | 257 ++++++++++++++++++++++++++----- index.html | 39 ++++- 5 files changed, 289 insertions(+), 64 deletions(-) diff --git a/controllers/controller.number.js b/controllers/controller.number.js index 235e24a..da8b201 100644 --- a/controllers/controller.number.js +++ b/controllers/controller.number.js @@ -18,6 +18,8 @@ var NumberController = function() { var max = arguments[4]; var step = arguments[5]; + console.log("NumberController", this.propertyName, arguments); + if (!step) { if (min != undefined && max != undefined) { step = (max-min)*0.01; @@ -28,8 +30,6 @@ var NumberController = function() { var numberField = document.createElement('input'); numberField.setAttribute('id', this.propertyName); - - // Little up and down arrows are pissing me off. numberField.setAttribute('type', 'text'); numberField.setAttribute('value', this.getValue()); @@ -47,7 +47,7 @@ var NumberController = function() { numberField.addEventListener('blur', function(e) { var val = parseFloat(this.value); if (!isNaN(val)) { - _this.updateValue(val); + _this.updateDisplay(); } else { this.value = _this.getValue(); } diff --git a/demo/demo.js b/demo/demo.js index 5bea7e1..62eca6e 100644 --- a/demo/demo.js +++ b/demo/demo.js @@ -47,6 +47,7 @@ function FizzyText(message) { var textAscent = 82; var textOffsetLeft = 80; var noiseScale = 300; + var frameTime = 30; var colors = ["#00aeff", "#0fa954", "#54396e", "#e61d5f"]; @@ -146,8 +147,16 @@ function FizzyText(message) { // the createBitmap function. this.message = message; + var loop = function() { + // Don't render if we don't see it. + // Would be cleaner if I dynamically acquired the top of the canvas. + if (document.body.scrollTop < height + 20) { + render(); + } + } + // This calls the render function every 30 milliseconds. - setInterval(render, 30); + setInterval(loop, frameTime); // This class is responsible for drawing and moving those little // colored dots. @@ -196,7 +205,7 @@ function FizzyText(message) { this.x += Math.cos(angle) * _this.speed + this.vx; this.y += -Math.sin(angle) * _this.speed + this.vy; - this.r = constrain(this.r, 0, _this.maxSize); + this.r = GUI.constrain(this.r, 0, _this.maxSize); // If we're tiny, keep moving around until we find a black // pixel. @@ -215,10 +224,5 @@ function FizzyText(message) { } - var constrain = function (v, o1, o2) { - if (v < o1) v = o1; - else if (v > o2) v = o2; - return v; - } } diff --git a/gui.css b/gui.css index d035320..ba8ec75 100644 --- a/gui.css +++ b/gui.css @@ -1,34 +1,43 @@ #guidat { +position: fixed; +top: 0; +left: 0; +width: 100%; +z-index: 1001; +text-align: right; +} + +.guidat { color: #fff; - position: fixed; - width: 280px; - z-index: 200; opacity: 0.97; - top: 0; - left: 100%; - margin-left: -300px; + text-align: left; + float: right; + margin-right: 20px; + margin-bottom: 20px; background-color: #fff; -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); -moz-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); } -#guidat, -#guidat input { +.guidat, +.guidat input { font: 9.5px Lucida Grande, sans-serif; } -#guidat-controllers { +.guidat-controllers { height: 300px; overflow-y: auto; overflow-x: hidden; background-color: rgba(0,0,0,0.1); + /* -moz-transition: height .2s ease-out; -webkit-transition: height .2s ease-out; transition: height .2s ease-out; + */ } -#guidat-toggle { +a.guidat-toggle { text-decoration: none; cursor: pointer; color: #fff; @@ -39,7 +48,7 @@ } -#guidat-toggle:hover { +a.guidat-toggle:hover { background-color: #000; } @@ -112,7 +121,7 @@ width: 148px; color: #00aeff; } -#guidat .guidat-controller.boolean input { +.guidat .guidat-controller.boolean input { margin-top: 6px; margin-right: 2px; font-size: 20px; diff --git a/gui.js b/gui.js index 14ffc15..87603b5 100644 --- a/gui.js +++ b/gui.js @@ -2,40 +2,178 @@ var GUI = function() { var _this = this; + var MIN_WIDTH = 200; + var MAX_WIDTH = 500; + var controllers = []; var listening = []; var autoListen = true; + var listenInterval; - var _this = this, open = false, - controllers = [], controllersWatched = []; + + // Sum total of heights of controllers in this gui + var controllerHeight; + + var curControllerContainerHeight = 0; + + // How big we get when we open + var openHeight; + + var _this = this, open = false; + + var name; + var width = 280; + + var resizeTo = 0; + var resizeTimeout; this.domElement = document.createElement('div'); - this.domElement.setAttribute('id', 'guidat'); + this.domElement.setAttribute('class', 'guidat'); + this.domElement.style.width = width+'px' - controllerContainer = document.createElement('div'); - controllerContainer.setAttribute('id', 'guidat-controllers'); + var controllerContainer = document.createElement('div'); + controllerContainer.setAttribute('class', 'guidat-controllers'); - // @doob - // I think this is way more elegant than the negative margin. - // Only wish we didn't have to see the scrollbar on its way open. - // Any thoughts? controllerContainer.style.height = '0px'; - toggleButton = document.createElement('a'); - toggleButton.setAttribute('id', 'guidat-toggle'); + var toggleButton = document.createElement('a'); + toggleButton.setAttribute('class', 'guidat-toggle'); toggleButton.setAttribute('href', '#'); toggleButton.innerHTML = "Show Controls"; - toggleButton.addEventListener('click', function(e) { - _this.toggle(); - e.preventDefault(); - }, false); + + var toggleDragged = false; + var dragDisplacementY = 0; + var togglePressed = false; + + var my, pmy, mx, pmx; + + var resize = function(e) { + if (!open) { + open = true; + curControllerContainerHeight = openHeight = 0; + } + pmy = my; + pmx = mx; + my = e.pageY; + mx = e.pageX; + + var dmy = my - pmy; + // TODO: Flip this if you want to resize to the left. + var dmx = pmx - mx; + + if (dmy > 0 && + curControllerContainerHeight > controllerHeight) { + var d = GUI.map(curControllerContainerHeight, controllerHeight, controllerHeight + 100, 1, 0); + dmy *= Math.pow(d, 1.5); + } + toggleDragged = true; + dragDisplacementY += dmy; + dragDisplacementX += dmx; + openHeight += dmy; + width += dmx; + curControllerContainerHeight += dmy; + controllerContainer.style.height = openHeight+'px'; + width = GUI.constrain(width, MIN_WIDTH, MAX_WIDTH); + _this.domElement.style.width = width+'px'; + checkForOverflow(); + }; + + toggleButton.addEventListener('mousedown', function(e) { + pmy = my = e.pageY; + pmx = mx = e.pageX; + togglePressed = true; + e.preventDefault(); + dragDisplacementY = 0; + dragDisplacementX = 0; + document.addEventListener('mousemove', resize, false); + return false; + + }, false); + + + toggleButton.addEventListener('click', function(e) { + e.preventDefault(); + return false; + }, false); + + + document.addEventListener('mouseup', function(e) { + + + if (togglePressed && !toggleDragged) { + + _this.toggle(); + + // Clears lingering slider column + _this.domElement.style.width = (width+1)+'px'; + setTimeout(function() { + _this.domElement.style.width = width+'px'; + }, 1); + } + + if (togglePressed && toggleDragged) { + + if (dragDisplacementX == 0) { + + // Clears lingering slider column + _this.domElement.style.width = (width+1)+'px'; + setTimeout(function() { + _this.domElement.style.width = width+'px'; + }, 1); + + } + + if (openHeight > controllerHeight) { + + clearTimeout(resizeTimeout); + openHeight = resizeTo = controllerHeight; + beginResize(); + + } else if (controllerContainer.children.length >= 1) { + + var singleControllerHeight = controllerContainer.children[0].offsetHeight; + clearTimeout(resizeTimeout); + var target = Math.round(curControllerContainerHeight/singleControllerHeight)*singleControllerHeight-1; + resizeTo = target; + if (resizeTo <= 0) { + _this.hide(); + openHeight = singleControllerHeight*2; + console.log("HIDING, wTF"); + } else { + openHeight = resizeTo; + beginResize(); + } + } + + + + } + + + document.removeEventListener('mousemove', resize, false); + e.preventDefault(); + toggleDragged = false; + togglePressed = false; + + return false; + + }, false); + this.domElement.appendChild(controllerContainer); this.domElement.appendChild(toggleButton); - + if (GUI.autoPlace) { + if(GUI.autoPlaceContainer == null) { + GUI.autoPlaceContainer = document.createElement('div'); + GUI.autoPlaceContainer.setAttribute("id", "guidat"); + document.body.appendChild(GUI.autoPlaceContainer); + } + GUI.autoPlaceContainer.appendChild(this.domElement); + } + this.autoListenIntervalTime = 1000/60; var createListenInterval = function() { @@ -70,7 +208,6 @@ var GUI = function() { }; this.listen = function(whoToListenTo) { - var arr = whoToListenTo || listening; for (var i in arr) { arr[i].updateDisplay(); @@ -100,11 +237,6 @@ var GUI = function() { return false; }; - var error = function(str) { - if (typeof console.log == 'function') { - console.error("[GUI ERROR] " + str); - } - }; var construct = function(constructor, args) { function F() { @@ -121,15 +253,15 @@ var GUI = function() { // Have we already added this? if (alreadyControlled(object, propertyName)) { - error("Controller for \"" + propertyName+"\" already added."); - return; + // GUI.error("Controller for \"" + propertyName+"\" already added."); + // return; } var value = object[propertyName]; // Does this value exist? Is it accessible? if (value == undefined) { - error(object + " either has no property \""+propertyName+"\", or the property is inaccessible."); + GUI.error(object + " either has no property \""+propertyName+"\", or the property is inaccessible."); return; } @@ -138,11 +270,11 @@ var GUI = function() { // Do we know how to deal with this data type? if (handler == undefined) { - error("Cannot create controller for data type \""+type+"\""); + GUI.error("Cannot create controller for data type \""+type+"\""); return; } - var args = [_this]; // Set first arg (parent) to this + var args = [this]; // Set first arg (parent) to this for (var j = 0; j < arguments.length; j++) { args.push(arguments[j]); } @@ -151,7 +283,7 @@ var GUI = function() { // Were we able to make the controller? if (!controllerObject) { - error("Error creating controller for \""+propertyName+"\"."); + GUI.error("Error creating controller for \""+propertyName+"\"."); return; } @@ -159,10 +291,30 @@ var GUI = function() { controllerContainer.appendChild(controllerObject.domElement); controllers.push(controllerObject); + // Compute sum height of controllers. + controllerHeight = 0; + for (var i in controllers) { + controllerHeight += controllers[i].domElement.offsetHeight; + } + + openHeight = controllerHeight; + + checkForOverflow(); + + + return controllerObject; } + var checkForOverflow = function() { + if (controllerHeight - 1 > openHeight) { + controllerContainer.style.overflowY = "auto"; + } else { + controllerContainer.style.overflowY = "hidden"; + } + } + var addHandlers = { "number": NumberController, "string": StringController, @@ -180,12 +332,6 @@ var GUI = function() { return false; }; - var error = function(str) { - if (typeof console.log == 'function') { - console.error("[GUI ERROR] " + str); - } - }; - var construct = function(constructor, args) { function F() { return constructor.apply(this, args); @@ -203,19 +349,43 @@ var GUI = function() { }; this.show = function() { - controllerContainer.style.height = '300px'; - toggleButton.innerHTML = "Hide Controls"; + toggleButton.innerHTML = name || "Hide Controls"; + resizeTo = openHeight; + clearTimeout(resizeTimeout); + beginResize(); open = true; } this.hide = function() { - controllerContainer.style.height = '0px'; - toggleButton.innerHTML = "Show Controls"; + toggleButton.innerHTML = name || "Show Controls"; + resizeTo = 0; + clearTimeout(resizeTimeout); + beginResize(); open = false; } + + this.name = function(n) { + name = n; + toggleButton.innerHTML = n; + } + + var beginResize = function() { + //console.log("Resizing from " + curControllerContainerHeight + " to " + resizeTo); + curControllerContainerHeight += (resizeTo - curControllerContainerHeight)*0.6; + if (Math.abs(curControllerContainerHeight-resizeTo) < 1) { + curControllerContainerHeight = resizeTo; + } else { + resizeTimeout = setTimeout(beginResize, 1000/30); + } + controllerContainer.style.height = Math.round(curControllerContainerHeight)+'px'; + + } }; +GUI.autoPlace = true; +GUI.autoPlaceContainer = null; + // Util functions GUI.makeUnselectable = function(elem) { @@ -234,7 +404,18 @@ GUI.makeSelectable = function(elem) { GUI.map = function(v, i1, i2, o1, o2) { var v = o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); + return v; +} + +GUI.constrain = function (v, o1, o2) { if (v < o1) v = o1; else if (v > o2) v = o2; return v; } + +GUI.error = function(str) { + if (typeof console.log == 'function') { + console.GUI.error("[GUI ERROR] " + str); + } +}; + diff --git a/index.html b/index.html index 640ed6f..86be472 100644 --- a/index.html +++ b/index.html @@ -26,7 +26,6 @@ var fizzyText = new FizzyText("gui-dat"); var gui = new GUI(); - document.body.appendChild( gui.domElement ); // Text field gui.add(fizzyText, "message"); @@ -72,7 +71,6 @@ window.onload = function() { var fizzyText = new FizzyText("gui-dat"); var gui = new GUI(); - document.body.appendChild( gui.domElement ); // Text field gui.add(fizzyText, "message"); @@ -108,13 +106,13 @@ window.onload = function() {

Listen for variable changes outside of the GUI

Let's say you have a variable that changes by itself from time to time. If you'd like the GUI to reflect those changes, use the listen() method.

-
gui.add(obj, "propName").listen();
+
gui.add(obj, "changingProperty").listen();

Advanced listening

By default, gui-dat will create an internal interval that checks for changes in the values you've marked with listen(). If you'd like to check for these changes in an interval of your own definition, use the following:

 gui.autoListen = false; // disables internal interval
-gui.add(obj, "propName").listen();
+gui.add(obj, "changingProperty").listen();
 
 // Make your own loop
 setInterval(function() {
@@ -133,6 +131,39 @@ setInterval(function() {
 	gui.listenAll(); // updates ALL values managed by this gui
 }, 1000 / 60);
 
+
+

Multiple panels and custom placement

+

You can instantiate multiple GUI objects and name them however you'd like.

+
+var gui1 = new GUI();
+var gui2 = new GUI();
+
+// The name function overwrites the "Show Controls" text.
+gui1.name("Utilities");
+gui2.name("Camera Placement");
+
+

By default, gui-dat panels will be automatically added to the HTML document and fixed to the top of the screen. You can disable this behavior / styling and append the gui DOM element to a container of your choosing.

+
// Notice this belongs to the GUI class (uppercase) 
+// and not an instance thereof.
+GUI.autoPlace = false; 
+
+var gui = new GUI();
+
+// Do some custom styles ...
+gui.domElement.style.position = "absolute";
+gui.domElement.style.top = "20px";
+gui.domElement.style.left = "20px";
+
+document.getElementById("my-gui-container").appendChild( gui.domElement );
+
+