'), $searchBox = $(''), $importButton = $(''), $exportButton = $(''), $pauseButton = $(''), $helpButton = $(''), $addCompositeButton = $('');
$buttonGroup.append($pauseButton, $importButton, $exportButton, $helpButton);
$inspectorContainer.prepend($buttonGroup, $searchBox, $addCompositeButton);
$body.prepend($inspectorContainer);
controls.pauseButton = $pauseButton;
controls.importButton = $importButton;
controls.exportButton = $exportButton;
controls.helpButton = $helpButton;
controls.searchBox = $searchBox;
controls.container = $inspectorContainer;
controls.addCompositeButton = $addCompositeButton;
controls.pauseButton.click(function() {
_setPaused(inspector, !inspector.isPaused);
});
controls.exportButton.click(function() {
_exportFile(inspector);
});
controls.importButton.click(function() {
_importFile(inspector);
});
controls.helpButton.click(function() {
_showHelp(inspector);
});
controls.addCompositeButton.click(function() {
_addNewComposite(inspector);
});
var searchTimeout;
controls.searchBox.keyup(function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
var value = controls.searchBox.val(), worldTree = controls.worldTree.data("jstree");
worldTree.search(value);
}, 250);
});
};
var _showHelp = function(inspector) {
var help = "Matter Tools\n\n";
help += "Drag nodes in the tree to move them between composites.\n";
help += "Use browser's developer console to inspect selected objects.\n";
help += "Note: selections only render if renderer supports it.\n\n";
help += "[shift + space] pause or play simulation.\n";
help += "[right click] and drag on empty space to select a region.\n";
help += "[right click] and drag on an object to move it.\n";
help += "[right click + shift] and drag to move whole selection.\n\n";
help += "[ctrl-c] to copy selected world objects.\n";
help += "[ctrl-v] to paste copied world objects to mouse position.\n";
help += "[del] or [backspace] delete selected objects.\n\n";
help += "[shift + s] scale-xy selected objects with mouse or arrows.\n";
help += "[shift + s + d] scale-x selected objects with mouse or arrows.\n";
help += "[shift + s + f] scale-y selected objects with mouse or arrows.\n";
help += "[shift + r] rotate selected objects with mouse or arrows.\n\n";
help += "[shift + q] set selected objects as static (can't be undone).\n";
help += "[shift + i] import objects.\n";
help += "[shift + o] export selected objects.\n";
help += "[shift + h] toggle Matter.Gui.\n";
help += "[shift + y] toggle auto-hide.\n";
help += "[shift + r] toggle auto-rewind on play/pause.\n\n";
help += "[shift + j] show this help message.";
alert(help);
};
var _initKeybinds = function(inspector) {
var engine = inspector.engine, controls = inspector.controls;
_key("shift+space", function() {
_setPaused(inspector, !inspector.isPaused);
});
_key("shift+o", function() {
_exportFile(inspector);
});
_key("shift+i", function() {
_importFile(inspector);
});
_key("shift+j", function() {
_showHelp(inspector);
});
_key("shift+y", function() {
inspector.autoHide = !inspector.autoHide;
$body.toggleClass("ins-auto-hide gui-auto-hide", inspector.autoHide);
});
_key("shift+r", function() {
inspector.autoRewind = !inspector.autoRewind;
if (!inspector.autoRewind) localStorage.removeItem("pauseState");
});
_key("shift+q", function() {
var worldTree = inspector.controls.worldTree.data("jstree");
for (var i = 0; i < inspector.selected.length; i++) {
var object = inspector.selected[i].data;
if (object.type === "body" && !object.isStatic) Body.setStatic(object, true);
}
});
_key("del", function() {
_deleteSelectedObjects(inspector);
});
_key("backspace", function() {
_deleteSelectedObjects(inspector);
});
_key("ctrl+c", function() {
_copySelectedObjects(inspector);
});
_key("ctrl+v", function() {
_pasteSelectedObjects(inspector);
});
$(document).unbind("keydown").bind("keydown", function(event) {
var doPrevent = false;
if (event.keyCode === 8) {
var d = event.srcElement || event.target;
if (d.tagName.toUpperCase() === "INPUT" && (d.type.toUpperCase() === "TEXT" || d.type.toUpperCase() === "PASSWORD" || d.type.toUpperCase() === "FILE" || d.type.toUpperCase() === "EMAIL" || d.type.toUpperCase() === "SEARCH") || d.tagName.toUpperCase() === "TEXTAREA") {
doPrevent = d.readOnly || d.disabled;
} else {
doPrevent = true;
}
}
if (doPrevent) {
event.preventDefault();
}
});
};
var _initTree = function(inspector) {
var engine = inspector.engine, controls = inspector.controls, deferTimeout;
var worldTreeOptions = {
core:{
check_callback:true
},
dnd:{
copy:false
},
search:{
show_only_matches:true,
fuzzy:false
},
types:{
"#":{
valid_children:[]
},
body:{
valid_children:[]
},
constraint:{
valid_children:[]
},
composite:{
valid_children:[]
},
bodies:{
valid_children:[ "body" ]
},
constraints:{
valid_children:[ "constraint" ]
},
composites:{
valid_children:[ "composite" ]
}
},
plugins:[ "dnd", "types", "unique", "search" ]
};
controls.worldTree = $('
').jstree(worldTreeOptions);
controls.container.prepend(controls.worldTree);
controls.worldTree.on("changed.jstree", function(event, data) {
var selected = [], worldTree = controls.worldTree.data("jstree");
if (data.action !== "select_node") return;
clearTimeout(deferTimeout);
deferTimeout = setTimeout(function() {
data.selected = worldTree.get_selected();
for (var i = 0; i < data.selected.length; i++) {
var nodeId = data.selected[i], objectType = nodeId.split("_")[0], objectId = nodeId.split("_")[1], worldObject = Composite.get(engine.world, objectId, objectType);
switch (objectType) {
case "body":
case "constraint":
case "composite":
selected.push(worldObject);
break;
}
}
_setSelectedObjects(inspector, selected);
}, 1);
});
$(document).on("dnd_stop.vakata", function(event, data) {
var worldTree = controls.worldTree.data("jstree"), nodes = data.data.nodes;
for (var i = 0; i < nodes.length; i++) {
var node = worldTree.get_node(nodes[i]), parentNode = worldTree.get_node(worldTree.get_parent(nodes[i])), prevCompositeId = node.data.compositeId, newCompositeId = parentNode.data.compositeId;
if (prevCompositeId === newCompositeId) continue;
var nodeId = nodes[i], objectType = nodeId.split("_")[0], objectId = nodeId.split("_")[1], worldObject = Composite.get(inspector.root, objectId, objectType), prevComposite = Composite.get(inspector.root, prevCompositeId, "composite"), newComposite = Composite.get(inspector.root, newCompositeId, "composite");
Composite.move(prevComposite, worldObject, newComposite);
}
});
controls.worldTree.on("dblclick.jstree", function(event, data) {
var worldTree = controls.worldTree.data("jstree"), selected = worldTree.get_selected();
for (var i = 0; i < selected.length; i++) {
var nodeId = selected[i], objectType = nodeId.split("_")[0], objectId = nodeId.split("_")[1], worldObject = Composite.get(engine.world, objectId, objectType);
switch (objectType) {
case "composite":
case "composites":
case "bodies":
case "constraints":
var node = worldTree.get_node(nodeId), children = worldTree.get_node(nodeId).children;
for (var j = 0; j < children.length; j++) worldTree.select_node(children[j], false);
break;
}
}
});
};
var _addBodyClass = function(inspector, classNames) {
if (inspector.bodyClass.indexOf(" " + classNames) === -1) {
$body.addClass(classNames);
inspector.bodyClass = " " + $body.attr("class");
}
};
var _removeBodyClass = function(inspector, classNames) {
var updateRequired = false, classes = classNames.split(" ");
for (var i = 0; i < classes.length; i++) {
updateRequired = inspector.bodyClass.indexOf(" " + classes[i]) !== -1;
if (updateRequired) break;
}
if (updateRequired) {
$body.removeClass(classNames);
inspector.bodyClass = " " + $body.attr("class");
}
};
var _getMousePosition = function(inspector) {
return Vector.add(inspector.mouse.position, inspector.offset);
};
var _initEngineEvents = function(inspector) {
var engine = inspector.engine, mouse = inspector.mouse, mousePosition = _getMousePosition(inspector), controls = inspector.controls;
Events.on(engine, "tick", function() {
mousePosition = _getMousePosition(inspector);
var mouseDelta = mousePosition.x - inspector.mousePrevPosition.x, keyDelta = _key.isPressed("up") + _key.isPressed("right") - _key.isPressed("down") - _key.isPressed("left"), delta = mouseDelta + keyDelta;
if (engine.world.isModified) {
var data = _generateCompositeTreeNode(inspector.root, null, true);
_updateTree(controls.worldTree.data("jstree"), data);
_setSelectedObjects(inspector, []);
}
if (inspector.selectStart !== null) {
inspector.selectEnd.x = mousePosition.x;
inspector.selectEnd.y = mousePosition.y;
Bounds.update(inspector.selectBounds, [ inspector.selectStart, inspector.selectEnd ]);
}
if (_key.shift && _key.isPressed("r")) {
var rotateSpeed = .03, angle = Math.max(-2, Math.min(2, delta)) * rotateSpeed;
_addBodyClass(inspector, "ins-cursor-rotate");
_rotateSelectedObjects(inspector, angle);
} else {
_removeBodyClass(inspector, "ins-cursor-rotate");
}
if (_key.shift && _key.isPressed("s")) {
var scaleSpeed = .02, scale = 1 + Math.max(-2, Math.min(2, delta)) * scaleSpeed;
_addBodyClass(inspector, "ins-cursor-scale");
if (_key.isPressed("d")) {
scaleX = scale;
scaleY = 1;
} else if (_key.isPressed("f")) {
scaleX = 1;
scaleY = scale;
} else {
scaleX = scaleY = scale;
}
_scaleSelectedObjects(inspector, scaleX, scaleY);
} else {
_removeBodyClass(inspector, "ins-cursor-scale");
}
if (mouse.button === 2 && !mouse.sourceEvents.mousedown && !mouse.sourceEvents.mouseup) {
_addBodyClass(inspector, "ins-cursor-move");
_moveSelectedObjects(inspector, mousePosition.x, mousePosition.y);
} else {
_removeBodyClass(inspector, "ins-cursor-move");
}
inspector.mousePrevPosition = Common.clone(mousePosition);
});
Events.on(engine, "mouseup", function(event) {
if (inspector.selectStart !== null) {
var selected = Query.region(Composite.allBodies(engine.world), inspector.selectBounds);
_setSelectedObjects(inspector, selected);
}
inspector.selectStart = null;
inspector.selectEnd = null;
Events.trigger(inspector, "selectEnd");
});
Events.on(engine, "mousedown", function(event) {
var engine = event.source, bodies = Composite.allBodies(engine.world), constraints = Composite.allConstraints(engine.world), isUnionSelect = _key.shift || _key.control, worldTree = inspector.controls.worldTree.data("jstree"), i;
if (mouse.button === 2) {
var hasSelected = false;
for (i = 0; i < bodies.length; i++) {
var body = bodies[i];
if (Bounds.contains(body.bounds, mousePosition) && Vertices.contains(body.vertices, mousePosition)) {
if (isUnionSelect) {
_addSelectedObject(inspector, body);
} else {
_setSelectedObjects(inspector, [ body ]);
}
hasSelected = true;
break;
}
}
if (!hasSelected) {
for (i = 0; i < constraints.length; i++) {
var constraint = constraints[i], bodyA = constraint.bodyA, bodyB = constraint.bodyB;
if (constraint.label.indexOf("Mouse Constraint") !== -1) continue;
var pointAWorld = constraint.pointA, pointBWorld = constraint.pointB;
if (bodyA) pointAWorld = Vector.add(bodyA.position, constraint.pointA);
if (bodyB) pointBWorld = Vector.add(bodyB.position, constraint.pointB);
if (!pointAWorld || !pointBWorld) continue;
var distA = Vector.magnitudeSquared(Vector.sub(mousePosition, pointAWorld)), distB = Vector.magnitudeSquared(Vector.sub(mousePosition, pointBWorld));
if (distA < 100 || distB < 100) {
if (isUnionSelect) {
_addSelectedObject(inspector, constraint);
} else {
_setSelectedObjects(inspector, [ constraint ]);
}
hasSelected = true;
break;
}
}
if (!hasSelected) {
worldTree.deselect_all(true);
_setSelectedObjects(inspector, []);
inspector.selectStart = Common.clone(mousePosition);
inspector.selectEnd = Common.clone(mousePosition);
Bounds.update(inspector.selectBounds, [ inspector.selectStart, inspector.selectEnd ]);
Events.trigger(inspector, "selectStart");
} else {
inspector.selectStart = null;
inspector.selectEnd = null;
}
}
}
if (mouse.button === 2 && inspector.selected.length > 0) {
_addBodyClass(inspector, "ins-cursor-move");
_updateSelectedMouseDownOffset(inspector);
}
});
Events.on(engine, "afterRender", function() {
var renderController = engine.render.controller, context = engine.render.context;
if (renderController.inspector) renderController.inspector(inspector, context);
});
};
var _deleteSelectedObjects = function(inspector) {
var objects = [], object, worldTree = inspector.controls.worldTree.data("jstree"), i;
for (i = 0; i < inspector.selected.length; i++) {
object = inspector.selected[i].data;
if (object !== inspector.engine.world) objects.push(object);
}
var selectedNodes = worldTree.get_selected();
for (i = 0; i < selectedNodes.length; i++) {
var node = worldTree.get_node(selectedNodes[i]);
if (node.type === "composite") {
node = worldTree.get_node(node.children[0]);
if (node.data) {
var compositeId = node.data.compositeId;
object = Composite.get(inspector.root, compositeId, "composite");
if (object && object !== inspector.engine.world) {
objects.push(object);
worldTree.delete_node(selectedNodes[i]);
}
}
}
}
Composite.remove(inspector.root, objects, true);
_setSelectedObjects(inspector, []);
};
var _copySelectedObjects = function(inspector) {
inspector.clipboard.length = 0;
for (var i = 0; i < inspector.selected.length; i++) {
var object = inspector.selected[i].data;
if (object.type !== "body") continue;
inspector.clipboard.push(object);
}
};
var _pasteSelectedObjects = function(inspector) {
var objects = [], worldTree = inspector.controls.worldTree.data("jstree");
for (var i = 0; i < inspector.clipboard.length; i++) {
var object = inspector.clipboard[i], clone = Gui.clone(inspector.serializer, object);
Body.translate(clone, {
x:50,
y:50
});
var node = worldTree.get_node(object.type + "_" + object.id, false), compositeId = node.data.compositeId, composite = Composite.get(inspector.engine.world, compositeId, "composite");
Composite.add(composite, clone);
objects.push(clone);
}
setTimeout(function() {
_setSelectedObjects(inspector, objects);
}, 200);
};
var _updateSelectedMouseDownOffset = function(inspector) {
var selected = inspector.selected, mouse = inspector.mouse, mousePosition = _getMousePosition(inspector), item, data;
for (var i = 0; i < selected.length; i++) {
item = selected[i];
data = item.data;
if (data.position) {
item.mousedownOffset = {
x:mousePosition.x - data.position.x,
y:mousePosition.y - data.position.y
};
} else if (data.pointA && !data.bodyA) {
item.mousedownOffset = {
x:mousePosition.x - data.pointA.x,
y:mousePosition.y - data.pointA.y
};
} else if (data.pointB && !data.bodyB) {
item.mousedownOffset = {
x:mousePosition.x - data.pointB.x,
y:mousePosition.y - data.pointB.y
};
}
}
};
var _moveSelectedObjects = function(inspector, x, y) {
var selected = inspector.selected, mouse = inspector.mouse, mousePosition = _getMousePosition(inspector), item, data;
for (var i = 0; i < selected.length; i++) {
item = selected[i];
data = item.data;
if (!item.mousedownOffset) continue;
switch (data.type) {
case "body":
var delta = {
x:x - data.position.x - item.mousedownOffset.x,
y:y - data.position.y - item.mousedownOffset.y
};
Body.translate(data, delta);
data.positionPrev.x = data.position.x;
data.positionPrev.y = data.position.y;
break;
case "constraint":
var point = data.pointA;
if (data.bodyA) point = data.pointB;
point.x = x - item.mousedownOffset.x;
point.y = y - item.mousedownOffset.y;
var initialPointA = data.bodyA ? Vector.add(data.bodyA.position, data.pointA) :data.pointA, initialPointB = data.bodyB ? Vector.add(data.bodyB.position, data.pointB) :data.pointB;
data.length = Vector.magnitude(Vector.sub(initialPointA, initialPointB));
break;
}
}
};
var _scaleSelectedObjects = function(inspector, scaleX, scaleY) {
var selected = inspector.selected, item, data;
for (var i = 0; i < selected.length; i++) {
item = selected[i];
data = item.data;
switch (data.type) {
case "body":
Body.scale(data, scaleX, scaleY, data.position);
if (data.circleRadius) data.circleRadius *= scaleX;
break;
}
}
};
var _rotateSelectedObjects = function(inspector, angle) {
var selected = inspector.selected, item, data;
for (var i = 0; i < selected.length; i++) {
item = selected[i];
data = item.data;
switch (data.type) {
case "body":
Body.rotate(data, angle);
break;
}
}
};
var _setPaused = function(inspector, isPaused) {
if (isPaused) {
if (inspector.autoRewind) {
_setSelectedObjects(inspector, []);
Gui.loadState(inspector.serializer, inspector.engine, "pauseState");
}
inspector.engine.timing.timeScale = 0;
inspector.isPaused = true;
inspector.controls.pauseButton.text("Play");
Events.trigger(inspector, "paused");
} else {
if (inspector.autoRewind) {
Gui.saveState(inspector.serializer, inspector.engine, "pauseState");
}
inspector.engine.timing.timeScale = 1;
inspector.isPaused = false;
inspector.controls.pauseButton.text("Pause");
Events.trigger(inspector, "play");
}
};
var _setSelectedObjects = function(inspector, objects) {
var worldTree = inspector.controls.worldTree.data("jstree"), selectedItems = [], data, i;
for (i = 0; i < inspector.selected.length; i++) {
data = inspector.selected[i].data;
worldTree.deselect_node(data.type + "_" + data.id, true);
}
inspector.selected = [];
console.clear();
for (i = 0; i < objects.length; i++) {
data = objects[i];
if (data) {
_addSelectedObject(inspector, data);
if (i < 5) {
console.log(data.label + " " + data.id + ": %O", data);
} else if (i === 6) {
console.warn("Omitted inspecting " + (objects.length - 5) + " more objects");
}
}
}
};
var _addSelectedObject = function(inspector, object) {
if (!object) return;
var worldTree = inspector.controls.worldTree.data("jstree");
inspector.selected.push({
data:object
});
worldTree.select_node(object.type + "_" + object.id, true);
};
var _updateTree = function(tree, data) {
data[0].state = data[0].state || {
opened:true
};
tree.settings.core.data = data;
tree.refresh(-1);
};
var _generateCompositeTreeNode = function(composite, compositeId, isRoot) {
var children = [], node = {
id:"composite_" + composite.id,
data:{
compositeId:compositeId
},
type:"composite",
text:(composite.label ? composite.label :"Composite") + " " + composite.id,
li_attr:{
"class":"jstree-node-type-composite"
}
};
var childNode = _generateCompositesTreeNode(composite.composites, composite.id);
childNode.id = "composites_" + composite.id;
children.push(childNode);
if (isRoot) return childNode.children;
childNode = _generateBodiesTreeNode(composite.bodies, composite.id);
childNode.id = "bodies_" + composite.id;
children.push(childNode);
childNode = _generateConstraintsTreeNode(composite.constraints, composite.id);
childNode.id = "constraints_" + composite.id;
children.push(childNode);
node.children = children;
return node;
};
var _generateCompositesTreeNode = function(composites, compositeId) {
var node = {
type:"composites",
text:"Composites",
data:{
compositeId:compositeId
},
children:[],
li_attr:{
"class":"jstree-node-type-composites"
}
};
for (var i = 0; i < composites.length; i++) {
var composite = composites[i];
node.children.push(_generateCompositeTreeNode(composite, compositeId));
}
return node;
};
var _generateBodiesTreeNode = function(bodies, compositeId) {
var node = {
type:"bodies",
text:"Bodies",
data:{
compositeId:compositeId
},
children:[],
li_attr:{
"class":"jstree-node-type-bodies"
}
};
for (var i = 0; i < bodies.length; i++) {
var body = bodies[i];
node.children.push({
type:"body",
id:"body_" + body.id,
data:{
compositeId:compositeId
},
text:(body.label ? body.label :"Body") + " " + body.id,
li_attr:{
"class":"jstree-node-type-body"
}
});
}
return node;
};
var _generateConstraintsTreeNode = function(constraints, compositeId) {
var node = {
type:"constraints",
text:"Constraints",
data:{
compositeId:compositeId
},
children:[],
li_attr:{
"class":"jstree-node-type-constraints"
}
};
for (var i = 0; i < constraints.length; i++) {
var constraint = constraints[i];
node.children.push({
type:"constraint",
id:"constraint_" + constraint.id,
data:{
compositeId:compositeId
},
text:(constraint.label ? constraint.label :"Constraint") + " " + constraint.id,
li_attr:{
"class":"jstree-node-type-constraint"
}
});
}
return node;
};
var _addNewComposite = function(inspector) {
var newComposite = Composite.create();
Composite.add(inspector.root, newComposite);
inspector.root.composites.splice(inspector.root.composites.length - 1, 1);
inspector.root.composites.unshift(newComposite);
Composite.setModified(inspector.engine.world, true, true, false);
};
var _exportFile = function(inspector) {
var engine = inspector.engine, toExport = [];
if (inspector.selected.length === 0) {
alert("No objects were selected, so export could not be created. Can only export objects that are in the World composite.");
return;
}
var fileName = "export-objects", exportComposite = Composite.create({
label:"Exported Objects"
});
for (var i = 0; i < inspector.selected.length; i++) {
var object = inspector.selected[i].data;
if (Composite.get(exportComposite, object.id, object.type)) continue;
Composite.add(exportComposite, object);
if (inspector.selected.length === 1) fileName = "export-" + object.label + "-" + object.id;
}
fileName = fileName.toLowerCase().replace(/[^\w\-]/g, "") + ".json";
var json = Gui.serialise(inspector.serializer, exportComposite, inspector.exportIndent);
if (_isWebkit) {
var blob = new Blob([ json ], {
type:"application/json"
}), anchor = document.createElement("a");
anchor.download = fileName;
anchor.href = (window.webkitURL || window.URL).createObjectURL(blob);
anchor.dataset.downloadurl = [ "application/json", anchor.download, anchor.href ].join(":");
anchor.click();
} else {
window.open("data:application/json;charset=utf-8," + escape(json));
}
Events.trigger(inspector, "export");
};
var _importFile = function(inspector) {
var engine = inspector.engine, element = document.createElement("div"), fileInput;
element.innerHTML = '';
fileInput = element.firstChild;
fileInput.addEventListener("change", function(e) {
var file = fileInput.files[0];
if (file.name.match(/\.(txt|json)$/)) {
var reader = new FileReader();
reader.onload = function(e) {
var importedComposite = inspector.serializer.parse(reader.result);
if (importedComposite) {
importedComposite.label = "Imported Objects";
Composite.rebase(importedComposite);
Composite.add(inspector.root, importedComposite);
inspector.root.composites.splice(inspector.root.composites.length - 1, 1);
inspector.root.composites.unshift(importedComposite);
var worldTree = inspector.controls.worldTree.data("jstree"), data = _generateCompositeTreeNode(inspector.root, null, true);
_updateTree(worldTree, data);
}
};
reader.readAsText(file);
} else {
alert("File not supported, .json or .txt JSON files only");
}
});
fileInput.click();
};
})();
MatterTools.Gui = Gui;
MatterTools.Inspector = Inspector;
if (typeof exports !== "undefined") {
if (typeof module !== "undefined" && module.exports) {
exports = module.exports = MatterTools;
}
exports.MatterTools = MatterTools;
}
if (typeof define === "function" && define.amd) {
define("MatterTools", [], function() {
return MatterTools;
});
}
if (typeof window === "object" && typeof window.document === "object") {
window.MatterTools = MatterTools;
}
})();
var dat = dat || {};
dat.gui = dat.gui || {};
dat.utils = dat.utils || {};
dat.controllers = dat.controllers || {};
dat.dom = dat.dom || {};
dat.color = dat.color || {};
dat.utils.css = function() {
return {
load:function(url, doc) {
doc = doc || document;
var link = doc.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = url;
doc.getElementsByTagName("head")[0].appendChild(link);
},
inject:function(css, doc) {
doc = doc || document;
var injected = document.createElement("style");
injected.type = "text/css";
injected.innerHTML = css;
doc.getElementsByTagName("head")[0].appendChild(injected);
}
};
}();
dat.utils.common = function() {
var ARR_EACH = Array.prototype.forEach;
var ARR_SLICE = Array.prototype.slice;
return {
BREAK:{},
extend:function(target) {
this.each(ARR_SLICE.call(arguments, 1), function(obj) {
for (var key in obj) if (!this.isUndefined(obj[key])) target[key] = obj[key];
}, this);
return target;
},
defaults:function(target) {
this.each(ARR_SLICE.call(arguments, 1), function(obj) {
for (var key in obj) if (this.isUndefined(target[key])) target[key] = obj[key];
}, this);
return target;
},
compose:function() {
var toCall = ARR_SLICE.call(arguments);
return function() {
var args = ARR_SLICE.call(arguments);
for (var i = toCall.length - 1; i >= 0; i--) {
args = [ toCall[i].apply(this, args) ];
}
return args[0];
};
},
each:function(obj, itr, scope) {
if (!obj) return;
if (ARR_EACH && obj.forEach && obj.forEach === ARR_EACH) {
obj.forEach(itr, scope);
} else if (obj.length === obj.length + 0) {
for (var key = 0, l = obj.length; key < l; key++) if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) return;
} else {
for (var key in obj) if (itr.call(scope, obj[key], key) === this.BREAK) return;
}
},
defer:function(fnc) {
setTimeout(fnc, 0);
},
toArray:function(obj) {
if (obj.toArray) return obj.toArray();
return ARR_SLICE.call(obj);
},
isUndefined:function(obj) {
return obj === undefined;
},
isNull:function(obj) {
return obj === null;
},
isNaN:function(obj) {
return obj !== obj;
},
isArray:Array.isArray || function(obj) {
return obj.constructor === Array;
},
isObject:function(obj) {
return obj === Object(obj);
},
isNumber:function(obj) {
return obj === obj + 0;
},
isString:function(obj) {
return obj === obj + "";
},
isBoolean:function(obj) {
return obj === false || obj === true;
},
isFunction:function(obj) {
return Object.prototype.toString.call(obj) === "[object Function]";
}
};
}();
dat.controllers.Controller = function(common) {
var Controller = function(object, property) {
this.initialValue = object[property];
this.domElement = document.createElement("div");
this.object = object;
this.property = property;
this.__onChange = undefined;
this.__onFinishChange = undefined;
};
common.extend(Controller.prototype, {
onChange:function(fnc) {
this.__onChange = fnc;
return this;
},
onFinishChange:function(fnc) {
this.__onFinishChange = fnc;
return this;
},
setValue:function(newValue) {
this.object[this.property] = newValue;
if (this.__onChange) {
this.__onChange.call(this, newValue);
}
this.updateDisplay();
return this;
},
getValue:function() {
return this.object[this.property];
},
updateDisplay:function() {
return this;
},
isModified:function() {
return this.initialValue !== this.getValue();
}
});
return Controller;
}(dat.utils.common);
dat.dom.dom = function(common) {
var EVENT_MAP = {
HTMLEvents:[ "change" ],
MouseEvents:[ "click", "mousemove", "mousedown", "mouseup", "mouseover" ],
KeyboardEvents:[ "keydown" ]
};
var EVENT_MAP_INV = {};
common.each(EVENT_MAP, function(v, k) {
common.each(v, function(e) {
EVENT_MAP_INV[e] = k;
});
});
var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/;
function cssValueToPixels(val) {
if (val === "0" || common.isUndefined(val)) return 0;
var match = val.match(CSS_VALUE_PIXELS);
if (!common.isNull(match)) {
return parseFloat(match[1]);
}
return 0;
}
var dom = {
makeSelectable:function(elem, selectable) {
if (elem === undefined || elem.style === undefined) return;
elem.onselectstart = selectable ? function() {
return false;
} :function() {};
elem.style.MozUserSelect = selectable ? "auto" :"none";
elem.style.KhtmlUserSelect = selectable ? "auto" :"none";
elem.unselectable = selectable ? "on" :"off";
},
makeFullscreen:function(elem, horizontal, vertical) {
if (common.isUndefined(horizontal)) horizontal = true;
if (common.isUndefined(vertical)) vertical = true;
elem.style.position = "absolute";
if (horizontal) {
elem.style.left = 0;
elem.style.right = 0;
}
if (vertical) {
elem.style.top = 0;
elem.style.bottom = 0;
}
},
fakeEvent:function(elem, eventType, params, aux) {
params = params || {};
var className = EVENT_MAP_INV[eventType];
if (!className) {
throw new Error("Event type " + eventType + " not supported.");
}
var evt = document.createEvent(className);
switch (className) {
case "MouseEvents":
var clientX = params.x || params.clientX || 0;
var clientY = params.y || params.clientY || 0;
evt.initMouseEvent(eventType, params.bubbles || false, params.cancelable || true, window, params.clickCount || 1, 0, 0, clientX, clientY, false, false, false, false, 0, null);
break;
case "KeyboardEvents":
var init = evt.initKeyboardEvent || evt.initKeyEvent;
common.defaults(params, {
cancelable:true,
ctrlKey:false,
altKey:false,
shiftKey:false,
metaKey:false,
keyCode:undefined,
charCode:undefined
});
init(eventType, params.bubbles || false, params.cancelable, window, params.ctrlKey, params.altKey, params.shiftKey, params.metaKey, params.keyCode, params.charCode);
break;
default:
evt.initEvent(eventType, params.bubbles || false, params.cancelable || true);
break;
}
common.defaults(evt, aux);
elem.dispatchEvent(evt);
},
bind:function(elem, event, func, bool) {
bool = bool || false;
if (elem.addEventListener) elem.addEventListener(event, func, bool); else if (elem.attachEvent) elem.attachEvent("on" + event, func);
return dom;
},
unbind:function(elem, event, func, bool) {
bool = bool || false;
if (elem.removeEventListener) elem.removeEventListener(event, func, bool); else if (elem.detachEvent) elem.detachEvent("on" + event, func);
return dom;
},
addClass:function(elem, className) {
if (elem.className === undefined) {
elem.className = className;
} else if (elem.className !== className) {
var classes = elem.className.split(/ +/);
if (classes.indexOf(className) == -1) {
classes.push(className);
elem.className = classes.join(" ").replace(/^\s+/, "").replace(/\s+$/, "");
}
}
return dom;
},
removeClass:function(elem, className) {
if (className) {
if (elem.className === undefined) {} else if (elem.className === className) {
elem.removeAttribute("class");
} else {
var classes = elem.className.split(/ +/);
var index = classes.indexOf(className);
if (index != -1) {
classes.splice(index, 1);
elem.className = classes.join(" ");
}
}
} else {
elem.className = undefined;
}
return dom;
},
hasClass:function(elem, className) {
return new RegExp("(?:^|\\s+)" + className + "(?:\\s+|$)").test(elem.className) || false;
},
getWidth:function(elem) {
var style = getComputedStyle(elem);
return cssValueToPixels(style["border-left-width"]) + cssValueToPixels(style["border-right-width"]) + cssValueToPixels(style["padding-left"]) + cssValueToPixels(style["padding-right"]) + cssValueToPixels(style["width"]);
},
getHeight:function(elem) {
var style = getComputedStyle(elem);
return cssValueToPixels(style["border-top-width"]) + cssValueToPixels(style["border-bottom-width"]) + cssValueToPixels(style["padding-top"]) + cssValueToPixels(style["padding-bottom"]) + cssValueToPixels(style["height"]);
},
getOffset:function(elem) {
var offset = {
left:0,
top:0
};
if (elem.offsetParent) {
do {
offset.left += elem.offsetLeft;
offset.top += elem.offsetTop;
} while (elem = elem.offsetParent);
}
return offset;
},
isActive:function(elem) {
return elem === document.activeElement && (elem.type || elem.href);
}
};
return dom;
}(dat.utils.common);
dat.controllers.OptionController = function(Controller, dom, common) {
var OptionController = function(object, property, options) {
OptionController.superclass.call(this, object, property);
var _this = this;
this.__select = document.createElement("select");
if (common.isArray(options)) {
var map = {};
common.each(options, function(element) {
map[element] = element;
});
options = map;
}
common.each(options, function(value, key) {
var opt = document.createElement("option");
opt.innerHTML = key;
opt.setAttribute("value", value);
_this.__select.appendChild(opt);
});
this.updateDisplay();
dom.bind(this.__select, "change", function() {
var desiredValue = this.options[this.selectedIndex].value;
_this.setValue(desiredValue);
});
this.domElement.appendChild(this.__select);
};
OptionController.superclass = Controller;
common.extend(OptionController.prototype, Controller.prototype, {
setValue:function(v) {
var toReturn = OptionController.superclass.prototype.setValue.call(this, v);
if (this.__onFinishChange) {
this.__onFinishChange.call(this, this.getValue());
}
return toReturn;
},
updateDisplay:function() {
this.__select.value = this.getValue();
return OptionController.superclass.prototype.updateDisplay.call(this);
}
});
return OptionController;
}(dat.controllers.Controller, dat.dom.dom, dat.utils.common);
dat.controllers.NumberController = function(Controller, common) {
var NumberController = function(object, property, params) {
NumberController.superclass.call(this, object, property);
params = params || {};
this.__min = params.min;
this.__max = params.max;
this.__step = params.step;
if (common.isUndefined(this.__step)) {
if (this.initialValue == 0) {
this.__impliedStep = 1;
} else {
this.__impliedStep = Math.pow(10, Math.floor(Math.log(this.initialValue) / Math.LN10)) / 10;
}
} else {
this.__impliedStep = this.__step;
}
this.__precision = numDecimals(this.__impliedStep);
};
NumberController.superclass = Controller;
common.extend(NumberController.prototype, Controller.prototype, {
setValue:function(v) {
if (this.__min !== undefined && v < this.__min) {
v = this.__min;
} else if (this.__max !== undefined && v > this.__max) {
v = this.__max;
}
if (this.__step !== undefined && v % this.__step != 0) {
v = Math.round(v / this.__step) * this.__step;
}
return NumberController.superclass.prototype.setValue.call(this, v);
},
min:function(v) {
this.__min = v;
return this;
},
max:function(v) {
this.__max = v;
return this;
},
step:function(v) {
this.__step = v;
this.__impliedStep = v;
this.__precision = numDecimals(v);
return this;
}
});
function numDecimals(x) {
x = x.toString();
if (x.indexOf(".") > -1) {
return x.length - x.indexOf(".") - 1;
} else {
return 0;
}
}
return NumberController;
}(dat.controllers.Controller, dat.utils.common);
dat.controllers.NumberControllerBox = function(NumberController, dom, common) {
var NumberControllerBox = function(object, property, params) {
this.__truncationSuspended = false;
NumberControllerBox.superclass.call(this, object, property, params);
var _this = this;
var prev_y;
this.__input = document.createElement("input");
this.__input.setAttribute("type", "text");
dom.bind(this.__input, "change", onChange);
dom.bind(this.__input, "blur", onBlur);
dom.bind(this.__input, "mousedown", onMouseDown);
dom.bind(this.__input, "keydown", function(e) {
if (e.keyCode === 13) {
_this.__truncationSuspended = true;
this.blur();
_this.__truncationSuspended = false;
}
});
function onChange() {
var attempted = parseFloat(_this.__input.value);
if (!common.isNaN(attempted)) _this.setValue(attempted);
}
function onBlur() {
onChange();
if (_this.__onFinishChange) {
_this.__onFinishChange.call(_this, _this.getValue());
}
}
function onMouseDown(e) {
dom.bind(window, "mousemove", onMouseDrag);
dom.bind(window, "mouseup", onMouseUp);
prev_y = e.clientY;
}
function onMouseDrag(e) {
var diff = prev_y - e.clientY;
_this.setValue(_this.getValue() + diff * _this.__impliedStep);
prev_y = e.clientY;
}
function onMouseUp() {
dom.unbind(window, "mousemove", onMouseDrag);
dom.unbind(window, "mouseup", onMouseUp);
}
this.updateDisplay();
this.domElement.appendChild(this.__input);
};
NumberControllerBox.superclass = NumberController;
common.extend(NumberControllerBox.prototype, NumberController.prototype, {
updateDisplay:function() {
this.__input.value = this.__truncationSuspended ? this.getValue() :roundToDecimal(this.getValue(), this.__precision);
return NumberControllerBox.superclass.prototype.updateDisplay.call(this);
}
});
function roundToDecimal(value, decimals) {
var tenTo = Math.pow(10, decimals);
return Math.round(value * tenTo) / tenTo;
}
return NumberControllerBox;
}(dat.controllers.NumberController, dat.dom.dom, dat.utils.common);
dat.controllers.NumberControllerSlider = function(NumberController, dom, css, common, styleSheet) {
var NumberControllerSlider = function(object, property, min, max, step) {
NumberControllerSlider.superclass.call(this, object, property, {
min:min,
max:max,
step:step
});
var _this = this;
this.__background = document.createElement("div");
this.__foreground = document.createElement("div");
dom.bind(this.__background, "mousedown", onMouseDown);
dom.addClass(this.__background, "slider");
dom.addClass(this.__foreground, "slider-fg");
function onMouseDown(e) {
dom.bind(window, "mousemove", onMouseDrag);
dom.bind(window, "mouseup", onMouseUp);
onMouseDrag(e);
}
function onMouseDrag(e) {
e.preventDefault();
var offset = dom.getOffset(_this.__background);
var width = dom.getWidth(_this.__background);
_this.setValue(map(e.clientX, offset.left, offset.left + width, _this.__min, _this.__max));
return false;
}
function onMouseUp() {
dom.unbind(window, "mousemove", onMouseDrag);
dom.unbind(window, "mouseup", onMouseUp);
if (_this.__onFinishChange) {
_this.__onFinishChange.call(_this, _this.getValue());
}
}
this.updateDisplay();
this.__background.appendChild(this.__foreground);
this.domElement.appendChild(this.__background);
};
NumberControllerSlider.superclass = NumberController;
NumberControllerSlider.useDefaultStyles = function() {
css.inject(styleSheet);
};
common.extend(NumberControllerSlider.prototype, NumberController.prototype, {
updateDisplay:function() {
var pct = (this.getValue() - this.__min) / (this.__max - this.__min);
this.__foreground.style.width = pct * 100 + "%";
return NumberControllerSlider.superclass.prototype.updateDisplay.call(this);
}
});
function map(v, i1, i2, o1, o2) {
return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
}
return NumberControllerSlider;
}(dat.controllers.NumberController, dat.dom.dom, dat.utils.css, dat.utils.common, "/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.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}");
dat.controllers.FunctionController = function(Controller, dom, common) {
var FunctionController = function(object, property, text) {
FunctionController.superclass.call(this, object, property);
var _this = this;
this.__button = document.createElement("div");
this.__button.innerHTML = text === undefined ? "Fire" :text;
dom.bind(this.__button, "click", function(e) {
e.preventDefault();
_this.fire();
return false;
});
dom.addClass(this.__button, "button");
this.domElement.appendChild(this.__button);
};
FunctionController.superclass = Controller;
common.extend(FunctionController.prototype, Controller.prototype, {
fire:function() {
if (this.__onChange) {
this.__onChange.call(this);
}
if (this.__onFinishChange) {
this.__onFinishChange.call(this, this.getValue());
}
this.getValue().call(this.object);
}
});
return FunctionController;
}(dat.controllers.Controller, dat.dom.dom, dat.utils.common);
dat.controllers.BooleanController = function(Controller, dom, common) {
var BooleanController = function(object, property) {
BooleanController.superclass.call(this, object, property);
var _this = this;
this.__prev = this.getValue();
this.__checkbox = document.createElement("input");
this.__checkbox.setAttribute("type", "checkbox");
dom.bind(this.__checkbox, "change", onChange, false);
this.domElement.appendChild(this.__checkbox);
this.updateDisplay();
function onChange() {
_this.setValue(!_this.__prev);
}
};
BooleanController.superclass = Controller;
common.extend(BooleanController.prototype, Controller.prototype, {
setValue:function(v) {
var toReturn = BooleanController.superclass.prototype.setValue.call(this, v);
if (this.__onFinishChange) {
this.__onFinishChange.call(this, this.getValue());
}
this.__prev = this.getValue();
return toReturn;
},
updateDisplay:function() {
if (this.getValue() === true) {
this.__checkbox.setAttribute("checked", "checked");
this.__checkbox.checked = true;
} else {
this.__checkbox.checked = false;
}
return BooleanController.superclass.prototype.updateDisplay.call(this);
}
});
return BooleanController;
}(dat.controllers.Controller, dat.dom.dom, dat.utils.common);
dat.color.toString = function(common) {
return function(color) {
if (color.a == 1 || common.isUndefined(color.a)) {
var s = color.hex.toString(16);
while (s.length < 6) {
s = "0" + s;
}
return "#" + s;
} else {
return "rgba(" + Math.round(color.r) + "," + Math.round(color.g) + "," + Math.round(color.b) + "," + color.a + ")";
}
};
}(dat.utils.common);
dat.color.interpret = function(toString, common) {
var result, toReturn;
var interpret = function() {
toReturn = false;
var original = arguments.length > 1 ? common.toArray(arguments) :arguments[0];
common.each(INTERPRETATIONS, function(family) {
if (family.litmus(original)) {
common.each(family.conversions, function(conversion, conversionName) {
result = conversion.read(original);
if (toReturn === false && result !== false) {
toReturn = result;
result.conversionName = conversionName;
result.conversion = conversion;
return common.BREAK;
}
});
return common.BREAK;
}
});
return toReturn;
};
var INTERPRETATIONS = [ {
litmus:common.isString,
conversions:{
THREE_CHAR_HEX:{
read:function(original) {
var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);
if (test === null) return false;
return {
space:"HEX",
hex:parseInt("0x" + test[1].toString() + test[1].toString() + test[2].toString() + test[2].toString() + test[3].toString() + test[3].toString())
};
},
write:toString
},
SIX_CHAR_HEX:{
read:function(original) {
var test = original.match(/^#([A-F0-9]{6})$/i);
if (test === null) return false;
return {
space:"HEX",
hex:parseInt("0x" + test[1].toString())
};
},
write:toString
},
CSS_RGB:{
read:function(original) {
var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
if (test === null) return false;
return {
space:"RGB",
r:parseFloat(test[1]),
g:parseFloat(test[2]),
b:parseFloat(test[3])
};
},
write:toString
},
CSS_RGBA:{
read:function(original) {
var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);
if (test === null) return false;
return {
space:"RGB",
r:parseFloat(test[1]),
g:parseFloat(test[2]),
b:parseFloat(test[3]),
a:parseFloat(test[4])
};
},
write:toString
}
}
}, {
litmus:common.isNumber,
conversions:{
HEX:{
read:function(original) {
return {
space:"HEX",
hex:original,
conversionName:"HEX"
};
},
write:function(color) {
return color.hex;
}
}
}
}, {
litmus:common.isArray,
conversions:{
RGB_ARRAY:{
read:function(original) {
if (original.length != 3) return false;
return {
space:"RGB",
r:original[0],
g:original[1],
b:original[2]
};
},
write:function(color) {
return [ color.r, color.g, color.b ];
}
},
RGBA_ARRAY:{
read:function(original) {
if (original.length != 4) return false;
return {
space:"RGB",
r:original[0],
g:original[1],
b:original[2],
a:original[3]
};
},
write:function(color) {
return [ color.r, color.g, color.b, color.a ];
}
}
}
}, {
litmus:common.isObject,
conversions:{
RGBA_OBJ:{
read:function(original) {
if (common.isNumber(original.r) && common.isNumber(original.g) && common.isNumber(original.b) && common.isNumber(original.a)) {
return {
space:"RGB",
r:original.r,
g:original.g,
b:original.b,
a:original.a
};
}
return false;
},
write:function(color) {
return {
r:color.r,
g:color.g,
b:color.b,
a:color.a
};
}
},
RGB_OBJ:{
read:function(original) {
if (common.isNumber(original.r) && common.isNumber(original.g) && common.isNumber(original.b)) {
return {
space:"RGB",
r:original.r,
g:original.g,
b:original.b
};
}
return false;
},
write:function(color) {
return {
r:color.r,
g:color.g,
b:color.b
};
}
},
HSVA_OBJ:{
read:function(original) {
if (common.isNumber(original.h) && common.isNumber(original.s) && common.isNumber(original.v) && common.isNumber(original.a)) {
return {
space:"HSV",
h:original.h,
s:original.s,
v:original.v,
a:original.a
};
}
return false;
},
write:function(color) {
return {
h:color.h,
s:color.s,
v:color.v,
a:color.a
};
}
},
HSV_OBJ:{
read:function(original) {
if (common.isNumber(original.h) && common.isNumber(original.s) && common.isNumber(original.v)) {
return {
space:"HSV",
h:original.h,
s:original.s,
v:original.v
};
}
return false;
},
write:function(color) {
return {
h:color.h,
s:color.s,
v:color.v
};
}
}
}
} ];
return interpret;
}(dat.color.toString, dat.utils.common);
dat.GUI = dat.gui.GUI = function(css, saveDialogueContents, styleSheet, controllerFactory, Controller, BooleanController, FunctionController, NumberControllerBox, NumberControllerSlider, OptionController, ColorController, requestAnimationFrame, CenteredDiv, dom, common) {
css.inject(styleSheet);
var CSS_NAMESPACE = "dg";
var HIDE_KEY_CODE = 72;
var CLOSE_BUTTON_HEIGHT = 20;
var DEFAULT_DEFAULT_PRESET_NAME = "Default";
var SUPPORTS_LOCAL_STORAGE = function() {
try {
return "localStorage" in window && window["localStorage"] !== null;
} catch (e) {
return false;
}
}();
var SAVE_DIALOGUE;
var auto_place_virgin = true;
var auto_place_container;
var hide = false;
var hideable_guis = [];
var GUI = function(params) {
var _this = this;
this.domElement = document.createElement("div");
this.__ul = document.createElement("ul");
this.domElement.appendChild(this.__ul);
dom.addClass(this.domElement, CSS_NAMESPACE);
this.__folders = {};
this.__controllers = [];
this.__rememberedObjects = [];
this.__rememberedObjectIndecesToControllers = [];
this.__listening = [];
params = params || {};
params = common.defaults(params, {
autoPlace:true,
width:GUI.DEFAULT_WIDTH
});
params = common.defaults(params, {
resizable:params.autoPlace,
hideable:params.autoPlace
});
if (!common.isUndefined(params.load)) {
if (params.preset) params.load.preset = params.preset;
} else {
params.load = {
preset:DEFAULT_DEFAULT_PRESET_NAME
};
}
if (common.isUndefined(params.parent) && params.hideable) {
hideable_guis.push(this);
}
params.resizable = common.isUndefined(params.parent) && params.resizable;
if (params.autoPlace && common.isUndefined(params.scrollable)) {
params.scrollable = true;
}
var use_local_storage = SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(this, "isLocal")) === "true";
var saveToLocalStorage;
Object.defineProperties(this, {
parent:{
get:function() {
return params.parent;
}
},
scrollable:{
get:function() {
return params.scrollable;
}
},
autoPlace:{
get:function() {
return params.autoPlace;
}
},
preset:{
get:function() {
if (_this.parent) {
return _this.getRoot().preset;
} else {
return params.load.preset;
}
},
set:function(v) {
if (_this.parent) {
_this.getRoot().preset = v;
} else {
params.load.preset = v;
}
setPresetSelectIndex(this);
_this.revert();
}
},
width:{
get:function() {
return params.width;
},
set:function(v) {
params.width = v;
setWidth(_this, v);
}
},
name:{
get:function() {
return params.name;
},
set:function(v) {
params.name = v;
if (title_row_name) {
title_row_name.innerHTML = params.name;
}
}
},
closed:{
get:function() {
return params.closed;
},
set:function(v) {
params.closed = v;
if (params.closed) {
dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
} else {
dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
}
this.onResize();
if (_this.__closeButton) {
_this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN :GUI.TEXT_CLOSED;
}
}
},
load:{
get:function() {
return params.load;
}
},
useLocalStorage:{
get:function() {
return use_local_storage;
},
set:function(bool) {
if (SUPPORTS_LOCAL_STORAGE) {
use_local_storage = bool;
if (bool) {
dom.bind(window, "unload", saveToLocalStorage);
} else {
dom.unbind(window, "unload", saveToLocalStorage);
}
localStorage.setItem(getLocalStorageHash(_this, "isLocal"), bool);
}
}
}
});
if (common.isUndefined(params.parent)) {
params.closed = false;
dom.addClass(this.domElement, GUI.CLASS_MAIN);
dom.makeSelectable(this.domElement, false);
if (SUPPORTS_LOCAL_STORAGE) {
if (use_local_storage) {
_this.useLocalStorage = true;
var saved_gui = localStorage.getItem(getLocalStorageHash(this, "gui"));
if (saved_gui) {
params.load = JSON.parse(saved_gui);
}
}
}
this.__closeButton = document.createElement("div");
this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
this.domElement.appendChild(this.__closeButton);
dom.bind(this.__closeButton, "click", function() {
_this.closed = !_this.closed;
});
} else {
if (params.closed === undefined) {
params.closed = true;
}
var title_row_name = document.createTextNode(params.name);
dom.addClass(title_row_name, "controller-name");
var title_row = addRow(_this, title_row_name);
var on_click_title = function(e) {
e.preventDefault();
_this.closed = !_this.closed;
return false;
};
dom.addClass(this.__ul, GUI.CLASS_CLOSED);
dom.addClass(title_row, "title");
dom.bind(title_row, "click", on_click_title);
if (!params.closed) {
this.closed = false;
}
}
if (params.autoPlace) {
if (common.isUndefined(params.parent)) {
if (auto_place_virgin) {
auto_place_container = document.createElement("div");
dom.addClass(auto_place_container, CSS_NAMESPACE);
dom.addClass(auto_place_container, GUI.CLASS_AUTO_PLACE_CONTAINER);
document.body.appendChild(auto_place_container);
auto_place_virgin = false;
}
auto_place_container.appendChild(this.domElement);
dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
}
if (!this.parent) setWidth(_this, params.width);
}
dom.bind(window, "resize", function() {
_this.onResize();
});
dom.bind(this.__ul, "webkitTransitionEnd", function() {
_this.onResize();
});
dom.bind(this.__ul, "transitionend", function() {
_this.onResize();
});
dom.bind(this.__ul, "oTransitionEnd", function() {
_this.onResize();
});
this.onResize();
if (params.resizable) {
addResizeHandle(this);
}
saveToLocalStorage = function() {
if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, "isLocal")) === "true") {
localStorage.setItem(getLocalStorageHash(_this, "gui"), JSON.stringify(_this.getSaveObject()));
}
};
this.saveToLocalStorageIfPossible = saveToLocalStorage;
var root = _this.getRoot();
function resetWidth() {
var root = _this.getRoot();
root.width += 1;
common.defer(function() {
root.width -= 1;
});
}
if (!params.parent) {
resetWidth();
}
};
GUI.toggleHide = function() {
hide = !hide;
common.each(hideable_guis, function(gui) {
gui.domElement.style.zIndex = hide ? -999 :999;
gui.domElement.style.opacity = hide ? 0 :1;
});
};
GUI.CLASS_AUTO_PLACE = "a";
GUI.CLASS_AUTO_PLACE_CONTAINER = "ac";
GUI.CLASS_MAIN = "main";
GUI.CLASS_CONTROLLER_ROW = "cr";
GUI.CLASS_TOO_TALL = "taller-than-window";
GUI.CLASS_CLOSED = "closed";
GUI.CLASS_CLOSE_BUTTON = "close-button";
GUI.CLASS_DRAG = "drag";
GUI.DEFAULT_WIDTH = 245;
GUI.TEXT_CLOSED = "Close Controls";
GUI.TEXT_OPEN = "Open Controls";
dom.bind(window, "keydown", function(e) {
if (document.activeElement.type !== "text" && (e.which === HIDE_KEY_CODE || e.keyCode == HIDE_KEY_CODE)) {
GUI.toggleHide();
}
}, false);
common.extend(GUI.prototype, {
add:function(object, property) {
return add(this, object, property, {
factoryArgs:Array.prototype.slice.call(arguments, 2)
});
},
addColor:function(object, property) {
return add(this, object, property, {
color:true
});
},
remove:function(controller) {
this.__ul.removeChild(controller.__li);
this.__controllers.slice(this.__controllers.indexOf(controller), 1);
var _this = this;
common.defer(function() {
_this.onResize();
});
},
destroy:function() {
if (this.autoPlace) {
auto_place_container.removeChild(this.domElement);
}
},
addFolder:function(name) {
if (this.__folders[name] !== undefined) {
throw new Error("You already have a folder in this GUI by the" + ' name "' + name + '"');
}
var new_gui_params = {
name:name,
parent:this
};
new_gui_params.autoPlace = this.autoPlace;
if (this.load && this.load.folders && this.load.folders[name]) {
new_gui_params.closed = this.load.folders[name].closed;
new_gui_params.load = this.load.folders[name];
}
var gui = new GUI(new_gui_params);
this.__folders[name] = gui;
var li = addRow(this, gui.domElement);
dom.addClass(li, "folder");
return gui;
},
open:function() {
this.closed = false;
},
close:function() {
this.closed = true;
},
onResize:function() {
var root = this.getRoot();
if (root.scrollable) {
var top = dom.getOffset(root.__ul).top;
var h = 0;
common.each(root.__ul.childNodes, function(node) {
if (!(root.autoPlace && node === root.__save_row)) h += dom.getHeight(node);
});
if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) {
dom.addClass(root.domElement, GUI.CLASS_TOO_TALL);
root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + "px";
} else {
dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL);
root.__ul.style.height = "auto";
}
}
if (root.__resize_handle) {
common.defer(function() {
root.__resize_handle.style.height = root.__ul.offsetHeight + "px";
});
}
if (root.__closeButton) {
root.__closeButton.style.width = root.width + "px";
}
},
remember:function() {
if (common.isUndefined(SAVE_DIALOGUE)) {
SAVE_DIALOGUE = new CenteredDiv();
SAVE_DIALOGUE.domElement.innerHTML = saveDialogueContents;
}
if (this.parent) {
throw new Error("You can only call remember on a top level GUI.");
}
var _this = this;
common.each(Array.prototype.slice.call(arguments), function(object) {
if (_this.__rememberedObjects.length == 0) {
addSaveMenu(_this);
}
if (_this.__rememberedObjects.indexOf(object) == -1) {
_this.__rememberedObjects.push(object);
}
});
if (this.autoPlace) {
setWidth(this, this.width);
}
},
getRoot:function() {
var gui = this;
while (gui.parent) {
gui = gui.parent;
}
return gui;
},
getSaveObject:function() {
var toReturn = this.load;
toReturn.closed = this.closed;
if (this.__rememberedObjects.length > 0) {
toReturn.preset = this.preset;
if (!toReturn.remembered) {
toReturn.remembered = {};
}
toReturn.remembered[this.preset] = getCurrentPreset(this);
}
toReturn.folders = {};
common.each(this.__folders, function(element, key) {
toReturn.folders[key] = element.getSaveObject();
});
return toReturn;
},
save:function() {
if (!this.load.remembered) {
this.load.remembered = {};
}
this.load.remembered[this.preset] = getCurrentPreset(this);
markPresetModified(this, false);
this.saveToLocalStorageIfPossible();
},
saveAs:function(presetName) {
if (!this.load.remembered) {
this.load.remembered = {};
this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true);
}
this.load.remembered[presetName] = getCurrentPreset(this);
this.preset = presetName;
addPresetOption(this, presetName, true);
this.saveToLocalStorageIfPossible();
},
revert:function(gui) {
common.each(this.__controllers, function(controller) {
if (!this.getRoot().load.remembered) {
controller.setValue(controller.initialValue);
} else {
recallSavedValue(gui || this.getRoot(), controller);
}
}, this);
common.each(this.__folders, function(folder) {
folder.revert(folder);
});
if (!gui) {
markPresetModified(this.getRoot(), false);
}
},
listen:function(controller) {
var init = this.__listening.length == 0;
this.__listening.push(controller);
if (init) updateDisplays(this.__listening);
}
});
function add(gui, object, property, params) {
if (object[property] === undefined) {
throw new Error("Object " + object + ' has no property "' + property + '"');
}
var controller;
if (params.color) {
controller = new ColorController(object, property);
} else {
var factoryArgs = [ object, property ].concat(params.factoryArgs);
controller = controllerFactory.apply(gui, factoryArgs);
}
if (params.before instanceof Controller) {
params.before = params.before.__li;
}
recallSavedValue(gui, controller);
dom.addClass(controller.domElement, "c");
var name = document.createElement("span");
dom.addClass(name, "property-name");
name.innerHTML = controller.property;
var container = document.createElement("div");
container.appendChild(name);
container.appendChild(controller.domElement);
var li = addRow(gui, container, params.before);
dom.addClass(li, GUI.CLASS_CONTROLLER_ROW);
dom.addClass(li, typeof controller.getValue());
augmentController(gui, li, controller);
gui.__controllers.push(controller);
return controller;
}
function addRow(gui, dom, liBefore) {
var li = document.createElement("li");
if (dom) li.appendChild(dom);
if (liBefore) {
gui.__ul.insertBefore(li, params.before);
} else {
gui.__ul.appendChild(li);
}
gui.onResize();
return li;
}
function augmentController(gui, li, controller) {
controller.__li = li;
controller.__gui = gui;
common.extend(controller, {
options:function(options) {
if (arguments.length > 1) {
controller.remove();
return add(gui, controller.object, controller.property, {
before:controller.__li.nextElementSibling,
factoryArgs:[ common.toArray(arguments) ]
});
}
if (common.isArray(options) || common.isObject(options)) {
controller.remove();
return add(gui, controller.object, controller.property, {
before:controller.__li.nextElementSibling,
factoryArgs:[ options ]
});
}
},
name:function(v) {
controller.__li.firstElementChild.firstElementChild.innerHTML = v;
return controller;
},
listen:function() {
controller.__gui.listen(controller);
return controller;
},
remove:function() {
controller.__gui.remove(controller);
return controller;
}
});
if (controller instanceof NumberControllerSlider) {
var box = new NumberControllerBox(controller.object, controller.property, {
min:controller.__min,
max:controller.__max,
step:controller.__step
});
common.each([ "updateDisplay", "onChange", "onFinishChange" ], function(method) {
var pc = controller[method];
var pb = box[method];
controller[method] = box[method] = function() {
var args = Array.prototype.slice.call(arguments);
pc.apply(controller, args);
return pb.apply(box, args);
};
});
dom.addClass(li, "has-slider");
controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild);
} else if (controller instanceof NumberControllerBox) {
var r = function(returned) {
if (common.isNumber(controller.__min) && common.isNumber(controller.__max)) {
controller.remove();
return add(gui, controller.object, controller.property, {
before:controller.__li.nextElementSibling,
factoryArgs:[ controller.__min, controller.__max, controller.__step ]
});
}
return returned;
};
controller.min = common.compose(r, controller.min);
controller.max = common.compose(r, controller.max);
} else if (controller instanceof BooleanController) {
dom.bind(li, "click", function() {
dom.fakeEvent(controller.__checkbox, "click");
});
dom.bind(controller.__checkbox, "click", function(e) {
e.stopPropagation();
});
} else if (controller instanceof FunctionController) {
dom.bind(li, "click", function() {
dom.fakeEvent(controller.__button, "click");
});
dom.bind(li, "mouseover", function() {
dom.addClass(controller.__button, "hover");
});
dom.bind(li, "mouseout", function() {
dom.removeClass(controller.__button, "hover");
});
} else if (controller instanceof ColorController) {
dom.addClass(li, "color");
controller.updateDisplay = common.compose(function(r) {
li.style.borderLeftColor = controller.__color.toString();
return r;
}, controller.updateDisplay);
controller.updateDisplay();
}
controller.setValue = common.compose(function(r) {
if (gui.getRoot().__preset_select && controller.isModified()) {
markPresetModified(gui.getRoot(), true);
}
return r;
}, controller.setValue);
}
function recallSavedValue(gui, controller) {
var root = gui.getRoot();
var matched_index = root.__rememberedObjects.indexOf(controller.object);
if (matched_index != -1) {
var controller_map = root.__rememberedObjectIndecesToControllers[matched_index];
if (controller_map === undefined) {
controller_map = {};
root.__rememberedObjectIndecesToControllers[matched_index] = controller_map;
}
controller_map[controller.property] = controller;
if (root.load && root.load.remembered) {
var preset_map = root.load.remembered;
var preset;
if (preset_map[gui.preset]) {
preset = preset_map[gui.preset];
} else if (preset_map[DEFAULT_DEFAULT_PRESET_NAME]) {
preset = preset_map[DEFAULT_DEFAULT_PRESET_NAME];
} else {
return;
}
if (preset[matched_index] && preset[matched_index][controller.property] !== undefined) {
var value = preset[matched_index][controller.property];
controller.initialValue = value;
controller.setValue(value);
}
}
}
}
function getLocalStorageHash(gui, key) {
return document.location.href + "." + key;
}
function addSaveMenu(gui) {
var div = gui.__save_row = document.createElement("li");
dom.addClass(gui.domElement, "has-save");
gui.__ul.insertBefore(div, gui.__ul.firstChild);
dom.addClass(div, "save-row");
var gears = document.createElement("span");
gears.innerHTML = " ";
dom.addClass(gears, "button gears");
var button = document.createElement("span");
button.innerHTML = "Save";
dom.addClass(button, "button");
dom.addClass(button, "save");
var button2 = document.createElement("span");
button2.innerHTML = "New";
dom.addClass(button2, "button");
dom.addClass(button2, "save-as");
var button3 = document.createElement("span");
button3.innerHTML = "Revert";
dom.addClass(button3, "button");
dom.addClass(button3, "revert");
var select = gui.__preset_select = document.createElement("select");
if (gui.load && gui.load.remembered) {
common.each(gui.load.remembered, function(value, key) {
addPresetOption(gui, key, key == gui.preset);
});
} else {
addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false);
}
dom.bind(select, "change", function() {
for (var index = 0; index < gui.__preset_select.length; index++) {
gui.__preset_select[index].innerHTML = gui.__preset_select[index].value;
}
gui.preset = this.value;
});
div.appendChild(select);
div.appendChild(gears);
div.appendChild(button);
div.appendChild(button2);
div.appendChild(button3);
if (SUPPORTS_LOCAL_STORAGE) {
var saveLocally = document.getElementById("dg-save-locally");
var explain = document.getElementById("dg-local-explain");
saveLocally.style.display = "block";
var localStorageCheckBox = document.getElementById("dg-local-storage");
if (localStorage.getItem(getLocalStorageHash(gui, "isLocal")) === "true") {
localStorageCheckBox.setAttribute("checked", "checked");
}
function showHideExplain() {
explain.style.display = gui.useLocalStorage ? "block" :"none";
}
showHideExplain();
dom.bind(localStorageCheckBox, "change", function() {
gui.useLocalStorage = !gui.useLocalStorage;
showHideExplain();
});
}
var newConstructorTextArea = document.getElementById("dg-new-constructor");
dom.bind(newConstructorTextArea, "keydown", function(e) {
if (e.metaKey && (e.which === 67 || e.keyCode == 67)) {
SAVE_DIALOGUE.hide();
}
});
dom.bind(gears, "click", function() {
newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2);
SAVE_DIALOGUE.show();
newConstructorTextArea.focus();
newConstructorTextArea.select();
});
dom.bind(button, "click", function() {
gui.save();
});
dom.bind(button2, "click", function() {
var presetName = prompt("Enter a new preset name.");
if (presetName) gui.saveAs(presetName);
});
dom.bind(button3, "click", function() {
gui.revert();
});
}
function addResizeHandle(gui) {
gui.__resize_handle = document.createElement("div");
common.extend(gui.__resize_handle.style, {
width:"6px",
marginLeft:"-3px",
height:"200px",
cursor:"ew-resize",
position:"absolute"
});
var pmouseX;
dom.bind(gui.__resize_handle, "mousedown", dragStart);
dom.bind(gui.__closeButton, "mousedown", dragStart);
gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild);
function dragStart(e) {
e.preventDefault();
pmouseX = e.clientX;
dom.addClass(gui.__closeButton, GUI.CLASS_DRAG);
dom.bind(window, "mousemove", drag);
dom.bind(window, "mouseup", dragStop);
return false;
}
function drag(e) {
e.preventDefault();
gui.width += pmouseX - e.clientX;
gui.onResize();
pmouseX = e.clientX;
return false;
}
function dragStop() {
dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG);
dom.unbind(window, "mousemove", drag);
dom.unbind(window, "mouseup", dragStop);
}
}
function setWidth(gui, w) {
gui.domElement.style.width = w + "px";
if (gui.__save_row && gui.autoPlace) {
gui.__save_row.style.width = w + "px";
}
if (gui.__closeButton) {
gui.__closeButton.style.width = w + "px";
}
}
function getCurrentPreset(gui, useInitialValues) {
var toReturn = {};
common.each(gui.__rememberedObjects, function(val, index) {
var saved_values = {};
var controller_map = gui.__rememberedObjectIndecesToControllers[index];
common.each(controller_map, function(controller, property) {
saved_values[property] = useInitialValues ? controller.initialValue :controller.getValue();
});
toReturn[index] = saved_values;
});
return toReturn;
}
function addPresetOption(gui, name, setSelected) {
var opt = document.createElement("option");
opt.innerHTML = name;
opt.value = name;
gui.__preset_select.appendChild(opt);
if (setSelected) {
gui.__preset_select.selectedIndex = gui.__preset_select.length - 1;
}
}
function setPresetSelectIndex(gui) {
for (var index = 0; index < gui.__preset_select.length; index++) {
if (gui.__preset_select[index].value == gui.preset) {
gui.__preset_select.selectedIndex = index;
}
}
}
function markPresetModified(gui, modified) {
var opt = gui.__preset_select[gui.__preset_select.selectedIndex];
if (modified) {
opt.innerHTML = opt.value + "*";
} else {
opt.innerHTML = opt.value;
}
}
function updateDisplays(controllerArray) {
if (controllerArray.length != 0) {
requestAnimationFrame(function() {
updateDisplays(controllerArray);
});
}
common.each(controllerArray, function(c) {
c.updateDisplay();
});
}
return GUI;
}(dat.utils.css, '
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to 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