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() {
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();
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);+
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 ); ++