totally resizeable :)

This commit is contained in:
George Michael Brower 2011-01-30 16:03:09 -07:00
parent 8ba92f76cd
commit 554767fa70
5 changed files with 289 additions and 64 deletions

View File

@ -18,6 +18,8 @@ var NumberController = function() {
var max = arguments[4]; var max = arguments[4];
var step = arguments[5]; var step = arguments[5];
console.log("NumberController", this.propertyName, arguments);
if (!step) { if (!step) {
if (min != undefined && max != undefined) { if (min != undefined && max != undefined) {
step = (max-min)*0.01; step = (max-min)*0.01;
@ -28,8 +30,6 @@ var NumberController = function() {
var numberField = document.createElement('input'); var numberField = document.createElement('input');
numberField.setAttribute('id', this.propertyName); numberField.setAttribute('id', this.propertyName);
// Little up and down arrows are pissing me off.
numberField.setAttribute('type', 'text'); numberField.setAttribute('type', 'text');
numberField.setAttribute('value', this.getValue()); numberField.setAttribute('value', this.getValue());
@ -47,7 +47,7 @@ var NumberController = function() {
numberField.addEventListener('blur', function(e) { numberField.addEventListener('blur', function(e) {
var val = parseFloat(this.value); var val = parseFloat(this.value);
if (!isNaN(val)) { if (!isNaN(val)) {
_this.updateValue(val); _this.updateDisplay();
} else { } else {
this.value = _this.getValue(); this.value = _this.getValue();
} }

View File

@ -47,6 +47,7 @@ function FizzyText(message) {
var textAscent = 82; var textAscent = 82;
var textOffsetLeft = 80; var textOffsetLeft = 80;
var noiseScale = 300; var noiseScale = 300;
var frameTime = 30;
var colors = ["#00aeff", "#0fa954", "#54396e", "#e61d5f"]; var colors = ["#00aeff", "#0fa954", "#54396e", "#e61d5f"];
@ -146,8 +147,16 @@ function FizzyText(message) {
// the createBitmap function. // the createBitmap function.
this.message = message; 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. // This calls the render function every 30 milliseconds.
setInterval(render, 30); setInterval(loop, frameTime);
// This class is responsible for drawing and moving those little // This class is responsible for drawing and moving those little
// colored dots. // colored dots.
@ -196,7 +205,7 @@ function FizzyText(message) {
this.x += Math.cos(angle) * _this.speed + this.vx; this.x += Math.cos(angle) * _this.speed + this.vx;
this.y += -Math.sin(angle) * _this.speed + this.vy; 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 // If we're tiny, keep moving around until we find a black
// pixel. // 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;
}
} }

33
gui.css
View File

@ -1,34 +1,43 @@
#guidat { #guidat {
color: #fff;
position: fixed; position: fixed;
width: 280px;
z-index: 200;
opacity: 0.97;
top: 0; top: 0;
left: 100%; left: 0;
margin-left: -300px; width: 100%;
z-index: 1001;
text-align: right;
}
.guidat {
color: #fff;
opacity: 0.97;
text-align: left;
float: right;
margin-right: 20px;
margin-bottom: 20px;
background-color: #fff; background-color: #fff;
-webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
-moz-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); box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.3);
} }
#guidat, .guidat,
#guidat input { .guidat input {
font: 9.5px Lucida Grande, sans-serif; font: 9.5px Lucida Grande, sans-serif;
} }
#guidat-controllers { .guidat-controllers {
height: 300px; height: 300px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
background-color: rgba(0,0,0,0.1); background-color: rgba(0,0,0,0.1);
/*
-moz-transition: height .2s ease-out; -moz-transition: height .2s ease-out;
-webkit-transition: height .2s ease-out; -webkit-transition: height .2s ease-out;
transition: height .2s ease-out; transition: height .2s ease-out;
*/
} }
#guidat-toggle { a.guidat-toggle {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
color: #fff; color: #fff;
@ -39,7 +48,7 @@
} }
#guidat-toggle:hover { a.guidat-toggle:hover {
background-color: #000; background-color: #000;
} }
@ -112,7 +121,7 @@ width: 148px;
color: #00aeff; color: #00aeff;
} }
#guidat .guidat-controller.boolean input { .guidat .guidat-controller.boolean input {
margin-top: 6px; margin-top: 6px;
margin-right: 2px; margin-right: 2px;
font-size: 20px; font-size: 20px;

251
gui.js
View File

@ -2,39 +2,177 @@ var GUI = function() {
var _this = this; var _this = this;
var MIN_WIDTH = 200;
var MAX_WIDTH = 500;
var controllers = []; var controllers = [];
var listening = []; var listening = [];
var autoListen = true; var autoListen = true;
var listenInterval; 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 = document.createElement('div');
this.domElement.setAttribute('id', 'guidat'); this.domElement.setAttribute('class', 'guidat');
this.domElement.style.width = width+'px'
controllerContainer = document.createElement('div'); var controllerContainer = document.createElement('div');
controllerContainer.setAttribute('id', 'guidat-controllers'); 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'; controllerContainer.style.height = '0px';
toggleButton = document.createElement('a'); var toggleButton = document.createElement('a');
toggleButton.setAttribute('id', 'guidat-toggle'); toggleButton.setAttribute('class', 'guidat-toggle');
toggleButton.setAttribute('href', '#'); toggleButton.setAttribute('href', '#');
toggleButton.innerHTML = "Show Controls"; toggleButton.innerHTML = "Show Controls";
toggleButton.addEventListener('click', function(e) {
_this.toggle(); 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(); 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); }, false);
this.domElement.appendChild(controllerContainer); this.domElement.appendChild(controllerContainer);
this.domElement.appendChild(toggleButton); 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; this.autoListenIntervalTime = 1000/60;
@ -70,7 +208,6 @@ var GUI = function() {
}; };
this.listen = function(whoToListenTo) { this.listen = function(whoToListenTo) {
var arr = whoToListenTo || listening; var arr = whoToListenTo || listening;
for (var i in arr) { for (var i in arr) {
arr[i].updateDisplay(); arr[i].updateDisplay();
@ -100,11 +237,6 @@ var GUI = function() {
return false; return false;
}; };
var error = function(str) {
if (typeof console.log == 'function') {
console.error("[GUI ERROR] " + str);
}
};
var construct = function(constructor, args) { var construct = function(constructor, args) {
function F() { function F() {
@ -121,15 +253,15 @@ var GUI = function() {
// Have we already added this? // Have we already added this?
if (alreadyControlled(object, propertyName)) { if (alreadyControlled(object, propertyName)) {
error("Controller for \"" + propertyName+"\" already added."); // GUI.error("Controller for \"" + propertyName+"\" already added.");
return; // return;
} }
var value = object[propertyName]; var value = object[propertyName];
// Does this value exist? Is it accessible? // Does this value exist? Is it accessible?
if (value == undefined) { 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; return;
} }
@ -138,11 +270,11 @@ var GUI = function() {
// Do we know how to deal with this data type? // Do we know how to deal with this data type?
if (handler == undefined) { if (handler == undefined) {
error("Cannot create controller for data type \""+type+"\""); GUI.error("Cannot create controller for data type \""+type+"\"");
return; 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++) { for (var j = 0; j < arguments.length; j++) {
args.push(arguments[j]); args.push(arguments[j]);
} }
@ -151,7 +283,7 @@ var GUI = function() {
// Were we able to make the controller? // Were we able to make the controller?
if (!controllerObject) { if (!controllerObject) {
error("Error creating controller for \""+propertyName+"\"."); GUI.error("Error creating controller for \""+propertyName+"\".");
return; return;
} }
@ -159,10 +291,30 @@ var GUI = function() {
controllerContainer.appendChild(controllerObject.domElement); controllerContainer.appendChild(controllerObject.domElement);
controllers.push(controllerObject); 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; return controllerObject;
} }
var checkForOverflow = function() {
if (controllerHeight - 1 > openHeight) {
controllerContainer.style.overflowY = "auto";
} else {
controllerContainer.style.overflowY = "hidden";
}
}
var addHandlers = { var addHandlers = {
"number": NumberController, "number": NumberController,
"string": StringController, "string": StringController,
@ -180,12 +332,6 @@ var GUI = function() {
return false; return false;
}; };
var error = function(str) {
if (typeof console.log == 'function') {
console.error("[GUI ERROR] " + str);
}
};
var construct = function(constructor, args) { var construct = function(constructor, args) {
function F() { function F() {
return constructor.apply(this, args); return constructor.apply(this, args);
@ -203,19 +349,43 @@ var GUI = function() {
}; };
this.show = function() { this.show = function() {
controllerContainer.style.height = '300px'; toggleButton.innerHTML = name || "Hide Controls";
toggleButton.innerHTML = "Hide Controls"; resizeTo = openHeight;
clearTimeout(resizeTimeout);
beginResize();
open = true; open = true;
} }
this.hide = function() { this.hide = function() {
controllerContainer.style.height = '0px'; toggleButton.innerHTML = name || "Show Controls";
toggleButton.innerHTML = "Show Controls"; resizeTo = 0;
clearTimeout(resizeTimeout);
beginResize();
open = false; 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 // Util functions
GUI.makeUnselectable = function(elem) { GUI.makeUnselectable = function(elem) {
@ -234,7 +404,18 @@ GUI.makeSelectable = function(elem) {
GUI.map = function(v, i1, i2, o1, o2) { GUI.map = function(v, i1, i2, o1, o2) {
var v = o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); var v = o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
return v;
}
GUI.constrain = function (v, o1, o2) {
if (v < o1) v = o1; if (v < o1) v = o1;
else if (v > o2) v = o2; else if (v > o2) v = o2;
return v; return v;
} }
GUI.error = function(str) {
if (typeof console.log == 'function') {
console.GUI.error("[GUI ERROR] " + str);
}
};

View File

@ -26,7 +26,6 @@
var fizzyText = new FizzyText("gui-dat"); var fizzyText = new FizzyText("gui-dat");
var gui = new GUI(); var gui = new GUI();
document.body.appendChild( gui.domElement );
// Text field // Text field
gui.add(fizzyText, "message"); gui.add(fizzyText, "message");
@ -72,7 +71,6 @@ window.onload = function() {
var fizzyText = new <a href="demo/demo.js">FizzyText</a>(&quot;gui-dat&quot;); var fizzyText = new <a href="demo/demo.js">FizzyText</a>(&quot;gui-dat&quot;);
var gui = new GUI(); var gui = new GUI();
document.body.appendChild( gui.domElement );
// Text field // Text field
gui.add(fizzyText, &quot;message&quot;); gui.add(fizzyText, &quot;message&quot;);
@ -108,13 +106,13 @@ window.onload = function() {
<hr/> <hr/>
<h2>Listen for variable changes <em>outside</em> of the GUI</h2> <h2>Listen for variable changes <em>outside</em> of the GUI</h2>
<p>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 <code>listen()</code> method.</p> <p>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 <code>listen()</code> method.</p>
<pre class="prettyprint">gui.add(obj, "propName").listen();</pre> <pre class="prettyprint">gui.add(obj, "changingProperty").listen();</pre>
<hr/> <hr/>
<h2>Advanced listening</h2> <h2>Advanced listening</h2>
<p>By default, <strong>gui-dat</strong> will create an internal interval that checks for changes in the values you've marked with <code>listen()</code>. If you'd like to check for these changes in an interval of your own definition, use the following: <p>By default, <strong>gui-dat</strong> will create an internal interval that checks for changes in the values you've marked with <code>listen()</code>. If you'd like to check for these changes in an interval of your own definition, use the following:
<pre class="prettyprint"> <pre class="prettyprint">
gui.autoListen = false; // disables internal interval gui.autoListen = false; // disables internal interval
gui.add(obj, "propName").listen(); gui.add(obj, "changingProperty").listen();
// Make your own loop // Make your own loop
setInterval(function() { setInterval(function() {
@ -133,6 +131,39 @@ setInterval(function() {
gui.listenAll(); // updates ALL values managed by this gui gui.listenAll(); // updates ALL values managed by this gui
}, 1000 / 60); }, 1000 / 60);
</pre> </pre>
<hr/>
<h2>Multiple panels and custom placement</h2>
<p>You can instantiate multiple <code>GUI</code> objects and name them however you'd like.</p>
<pre class="prettyprint">
var gui1 = new GUI();
var gui2 = new GUI();
// The name function overwrites the "Show Controls" text.
gui1.name("Utilities");
gui2.name("Camera Placement");
</pre>
<p>By default, <strong>gui-dat</strong> 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.</p>
<pre class="prettyprint">// 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 );
</pre>
<!--
<hr/>
<h2>Secrets</h2>
<ol id="secrets">
<strong>gui-dat</strong> panels are resizeable. <br/>
Press H to make panels invisible.
</ol>
-->
<footer> <footer>
Initiated by <a href="http://georgemichaelbrower.com/">George Michael Brower</a> and <a href="http://jonobr1.com/">Jono Brandel</a> of the Data Arts Team, Google Creative Lab. Initiated by <a href="http://georgemichaelbrower.com/">George Michael Brower</a> and <a href="http://jonobr1.com/">Jono Brandel</a> of the Data Arts Team, Google Creative Lab.
</footer> </footer>