//enclose in a scope
(function(){
function Button( value, options )
{
options = options || {};
if(typeof(options) === "function")
options = { callback: options };
var that = this;
var element = document.createElement("div");
element.className = "litegui button";
this.root = element;
var button = document.createElement("button");
button.className = "litebutton";
this.content = button;
element.appendChild(button);
button.innerHTML = value;
button.addEventListener("click", function(e) {
that.click();
});
this.click = function()
{
if(options.callback)
options.callback.call(that);
}
}
LiteGUI.Button = Button;
/**
* SearchBox
*
* @class SearchBox
* @constructor
* @param {*} value
* @param {Object} options
*/
function SearchBox( value, options )
{
options = options || {};
var element = document.createElement("div");
element.className = "litegui searchbox";
var placeholder = (options.placeholder != null ? options.placeholder : "Search");
element.innerHTML = "<input value='"+value+"' placeholder='"+ placeholder +"'/>";
this.input = element.querySelector("input");
this.root = element;
var that = this;
$(this.input).change( function(e) {
var value = e.target.value;
if(options.callback)
options.callback.call(that,value);
});
}
SearchBox.prototype.setValue = function(v) { $(this.input).val(v).change(); };
SearchBox.prototype.getValue = function() { return $(this.input).val(); };
LiteGUI.SearchBox = SearchBox;
/**
* ContextMenu
*
* @class ContextMenu
* @constructor
* @param {Array} values (allows object { title: "Nice text", callback: function ... })
* @param {Object} options [optional] Some options:\
* - title: title to show on top of the menu
* - callback: function to call when an option is clicked, it receives the item information
* - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback
* - event: you can pass a MouseEvent, this way the ContextMenu appears in that position
*/
function ContextMenu( values, options )
{
options = options || {};
this.options = options;
var that = this;
//to link a menu with its parent
if(options.parentMenu)
{
if( options.parentMenu.constructor !== this.constructor )
{
console.error("parentMenu must be of class ContextMenu, ignoring it");
options.parentMenu = null;
}
else
{
this.parentMenu = options.parentMenu;
this.parentMenu.lock = true;
this.parentMenu.current_submenu = this;
}
}
if(options.event && options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent)
{
console.error("Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it.");
options.event = null;
}
var root = document.createElement("div");
root.className = "litecontextmenu litemenubar-panel";
root.style.minWidth = 100;
root.style.minHeight = 100;
root.style.pointerEvents = "none";
setTimeout( function() { root.style.pointerEvents = "auto"; },100); //delay so the mouse up event is not caugh by this element
//this prevents the default context browser menu to open in case this menu was created when pressing right button
root.addEventListener("mouseup", function(e){
e.preventDefault(); return true;
}, true);
root.addEventListener("contextmenu", function(e) {
if(e.button != 2) //right button
return false;
e.preventDefault();
return false;
},true);
root.addEventListener("mousedown", function(e){
if(e.button == 2)
{
that.close();
e.preventDefault(); return true;
}
}, true);
this.root = root;
//title
if(options.title)
{
var element = document.createElement("div");
element.className = "litemenu-title";
element.innerHTML = options.title;
root.appendChild(element);
}
//entries
var num = 0;
for(var i in values)
{
var name = values.constructor == Array ? values[i] : i;
if( name != null && name.constructor !== String )
name = name.content === undefined ? String(name) : name.content;
var value = values[i];
this.addItem( name, value, options );
num++;
}
//close on leave
root.addEventListener("mouseleave", function(e) {
if(that.lock)
return;
that.close(e);
});
//insert before checking position
var root_document = document;
if(options.event)
root_document = options.event.target.ownerDocument;
if(!root_document)
root_document = document;
root_document.body.appendChild(root);
//compute best position
var left = options.left || 0;
var top = options.top || 0;
if(options.event)
{
if( options.event.constructor !== MouseEvent && options.event.constructor !== CustomEvent )
{
console.warn("Event passed to ContextMenu is not of type MouseEvent");
options.event = null;
}
else
{
left = (options.event.pageX - 10);
top = (options.event.pageY - 10);
if(options.title)
top -= 20;
if(options.parentMenu)
{
var rect = options.parentMenu.root.getBoundingClientRect();
left = rect.left + rect.width;
}
var body_rect = document.body.getBoundingClientRect();
var root_rect = root.getBoundingClientRect();
if(left > (body_rect.width - root_rect.width - 10))
left = (body_rect.width - root_rect.width - 10);
if(top > (body_rect.height - root_rect.height - 10))
top = (body_rect.height - root_rect.height - 10);
}
}
root.style.left = left + "px";
root.style.top = top + "px";
}
ContextMenu.prototype.addItem = function( name, value, options )
{
var that = this;
options = options || {};
var element = document.createElement("div");
element.className = "litemenu-entry submenu";
var disabled = false;
if(value === null)
{
element.classList.add("separator");
//element.innerHTML = "<hr/>"
//continue;
}
else
{
element.innerHTML = value && value.title ? value.title : name;
element.value = value;
if(value)
{
if(value.disabled)
{
disabled = true;
element.classList.add("disabled");
}
if(value.submenu || value.has_submenu)
element.classList.add("has_submenu");
}
if(typeof(value) == "function")
{
element.dataset["value"] = name;
element.onclick_callback = value;
}
else
element.dataset["value"] = value;
}
this.root.appendChild(element);
if(!disabled)
element.addEventListener("click", inner_onclick);
if(options.autoopen)
element.addEventListener("mouseenter", inner_over);
function inner_over(e)
{
var value = this.value;
if(!value || !value.has_submenu)
return;
inner_onclick.call(this,e);
}
//menu option clicked
function inner_onclick(e) {
var value = this.value;
var close_parent = true;
if(that.current_submenu)
that.current_submenu.close(e);
//global callback
if(options.callback)
{
var r = options.callback.call(that, value, options, e );
if(r === true)
close_parent = false;
}
//special cases
if(value)
{
if (value.callback && !options.ignore_item_callbacks && value.disabled !== true ) //item callback
{
var r = value.callback.call( this, value, options, e, that );
if(r === true)
close_parent = false;
}
if(value.submenu)
{
if(!value.submenu.options)
throw("ContextMenu submenu needs options");
var submenu = new LiteGUI.ContextMenu( value.submenu.options, {
callback: value.submenu.callback,
event: e,
parentMenu: that,
ignore_item_callbacks: value.submenu.ignore_item_callbacks,
title: value.submenu.title,
autoopen: options.autoopen
});
close_parent = false;
}
}
if(close_parent && !that.lock)
that.close();
}
return element;
}
ContextMenu.prototype.close = function(e, ignore_parent_menu)
{
if(this.root.parentNode)
this.root.parentNode.removeChild( this.root );
if(this.parentMenu && !ignore_parent_menu)
{
this.parentMenu.lock = false;
this.parentMenu.current_submenu = null;
if( e === undefined )
this.parentMenu.close();
else if( e && !LiteGUI.isCursorOverElement( e, this.parentMenu.root) )
LiteGUI.trigger( this.parentMenu.root, "mouseleave", e );
}
if(this.current_submenu)
this.current_submenu.close(e, true);
}
//returns the top most menu
ContextMenu.prototype.getTopMenu = function()
{
if( this.options.parentMenu )
return this.options.parentMenu.getTopMenu();
return this;
}
ContextMenu.prototype.getFirstEvent = function()
{
if( this.options.parentMenu )
return this.options.parentMenu.getFirstEvent();
return this.options.event;
}
LiteGUI.ContextMenu = ContextMenu;
LiteGUI.ContextualMenu = ContextMenu; //LEGACY: REMOVE
//the tiny box to expand the children of a node
function Checkbox( value, on_change)
{
var that = this;
this.value = value;
var root = this.root = document.createElement("span");
root.className = "litecheckbox inputfield";
root.dataset["value"] = value;
var element = this.element = document.createElement("span");
element.className = "fixed flag checkbox "+(value ? "on" : "off");
root.appendChild( element );
root.addEventListener("click", onClick.bind(this) );
function onClick(e) {
this.setValue( this.root.dataset["value"] != "true" );
e.preventDefault();
e.stopPropagation();
}
this.setValue = function(v)
{
if(this.value === v)
return;
if( this.root.dataset["value"] == v.toString())
return;
this.root.dataset["value"] = v;
if(v)
{
this.element.classList.remove("off");
this.element.classList.add("on");
}
else
{
this.element.classList.remove("on");
this.element.classList.add("off");
}
var old_value = this.value;
this.value = v;
if(on_change)
on_change( v, old_value );
}
this.getValue = function()
{
return this.value;
//return this.root.dataset["value"] == "true";
}
}
LiteGUI.Checkbox = Checkbox;
//the tiny box to expand the children of a node
function createLitebox(state, on_change)
{
var element = document.createElement("span");
element.className = "listbox " + (state ? "listopen" : "listclosed");
element.innerHTML = state ? "▼" : "►";
element.dataset["value"] = state ? "open" : "closed";
element.addEventListener("click", onClick );
element.on_change_callback = on_change;
element.setEmpty = function(v)
{
if(v)
this.classList.add("empty");
else
this.classList.remove("empty");
}
element.expand = function()
{
this.setValue(true);
}
element.collapse = function()
{
this.setValue(false);
}
element.setValue = function(v)
{
if(this.dataset["value"] == (v ? "open" : "closed"))
return;
if(!v)
{
this.dataset["value"] = "closed";
this.innerHTML = "►";
this.classList.remove("listopen");
this.classList.add("listclosed");
}
else
{
this.dataset["value"] = "open";
this.innerHTML = "▼";
this.classList.add("listopen");
this.classList.remove("listclosed");
}
if(on_change)
on_change( this.dataset["value"] );
}
element.getValue = function()
{
return this.dataset["value"];
}
function onClick(e) {
//console.log("CLICK");
var box = e.target;
box.setValue( this.dataset["value"] == "open" ? false : true );
if(this.stopPropagation)
e.stopPropagation();
}
return element;
}
LiteGUI.createLitebox = createLitebox;
/**
* List
*
* @class List
* @constructor
* @param {String} id
* @param {Array} values
* @param {Object} options
*/
function List( id, items, options )
{
options = options || {};
var root = this.root = document.createElement("ul");
root.id = id;
root.className = "litelist";
this.items = [];
var that = this;
this.callback = options.callback;
//walk over every item in the list
for(var i in items)
{
var item = document.createElement("li");
item.className = "list-item";
item.data = items[i];
item.dataset["value"] = items[i];
var content = "";
if(typeof(items[i]) == "string")
content = items[i] + "<span class='arrow'></span>";
else
{
content = (items[i].name || items[i].title || "") + "<span class='arrow'></span>";
if(items[i].id)
item.id = items[i].id;
}
item.innerHTML = content;
item.addEventListener("click", function() {
$(root).find(".list-item.selected").removeClass("selected");
this.classList.add("selected");
$(that.root).trigger("wchanged", this);
if(that.callback)
that.callback( this.data );
});
root.appendChild(item);
}
if(options.parent)
{
if(options.parent.root)
options.parent.root.appendChild( root );
else
options.parent.appendChild( root );
}
}
List.prototype.getSelectedItem = function()
{
return this.root.querySelector(".list-item.selected");
}
List.prototype.setSelectedItem = function( name )
{
var items = this.root.querySelectorAll(".list-item");
for(var i = 0; i < items.length; i++)
{
var item = items[i];
if(item.data == name)
{
LiteGUI.trigger( item, "click" );
break;
}
}
}
LiteGUI.List = List;
/**
* Slider
*
* @class Slider
* @constructor
* @param {Number} value
* @param {Object} options
*/
function Slider(value, options)
{
options = options || {};
var canvas = document.createElement("canvas");
canvas.className = "slider " + (options.extraclass ? options.extraclass : "");
canvas.width = 100;
canvas.height = 1;
canvas.style.position = "relative";
canvas.style.width = "calc( 100% - 2em )";
canvas.style.height = "1.2em";
this.root = canvas;
var that = this;
this.value = value;
this.setValue = function(value, skip_event)
{
//var width = canvas.getClientRects()[0].width;
var ctx = canvas.getContext("2d");
var min = options.min || 0.0;
var max = options.max || 1.0;
if(value < min) value = min;
else if(value > max) value = max;
var range = max - min;
var norm = (value - min) / range;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "#999";
ctx.fillRect(0,0, canvas.width * norm, canvas.height);
ctx.fillStyle = "#DA2";
ctx.fillRect(canvas.width * norm - 1,0,2, canvas.height);
if(value != this.value)
{
this.value = value;
if(!skip_event)
{
LiteGUI.trigger(this.root, "change", value );
if(this.onChange)
this.onChange( value );
}
}
}
function setFromX(x)
{
var width = canvas.getClientRects()[0].width;
var norm = x / width;
var min = options.min || 0.0;
var max = options.max || 1.0;
var range = max - min;
that.setValue( range * norm + min );
}
var doc_binded = null;
canvas.addEventListener("mousedown", function(e) {
var mouseX, mouseY;
if(e.offsetX) { mouseX = e.offsetX; mouseY = e.offsetY; }
else if(e.layerX) { mouseX = e.layerX; mouseY = e.layerY; }
setFromX(mouseX);
doc_binded = canvas.ownerDocument;
doc_binded.addEventListener("mousemove", onMouseMove );
doc_binded.addEventListener("mouseup", onMouseUp );
});
function onMouseMove(e)
{
var rect = canvas.getClientRects()[0];
var x = e.x === undefined ? e.pageX : e.x;
var mouseX = x - rect.left;
setFromX(mouseX);
e.preventDefault();
return false;
}
function onMouseUp(e)
{
var doc = doc_binded || document;
doc_binded = null;
doc.removeEventListener("mousemove", onMouseMove );
doc.removeEventListener("mouseup", onMouseUp );
e.preventDefault();
return false;
}
this.setValue(value);
}
LiteGUI.Slider = Slider;
/**
* LineEditor
*
* @class LineEditor
* @constructor
* @param {Number} value
* @param {Object} options
*/
function LineEditor(value, options)
{
options = options || {};
var element = document.createElement("div");
element.className = "curve " + (options.extraclass ? options.extraclass : "");
element.style.minHeight = "50px";
element.style.width = options.width || "100%";
element.bgcolor = options.bgcolor || "#222";
element.pointscolor = options.pointscolor || "#5AF";
element.linecolor = options.linecolor || "#444";
element.value = value || [];
element.xrange = options.xrange || [0,1]; //min,max
element.yrange = options.yrange || [0,1]; //min,max
element.defaulty = options.defaulty != null ? options.defaulty : 0.5;
element.no_trespassing = options.no_trespassing || false;
element.show_samples = options.show_samples || 0;
element.options = options;
var canvas = document.createElement("canvas");
canvas.width = options.width || 200;
canvas.height = options.height || 50;
element.appendChild(canvas);
element.canvas = canvas;
$(canvas).bind("mousedown",onmousedown);
$(element).resize(onresize);
element.getValueAt = function(x)
{
if(x < element.xrange[0] || x > element.xrange[1])
return element.defaulty;
var last = [ element.xrange[0], element.defaulty ];
var f = 0;
for(var i = 0; i < element.value.length; i += 1)
{
var v = element.value[i];
if(x == v[0]) return v[1];
if(x < v[0])
{
f = (x - last[0]) / (v[0] - last[0]);
return last[1] * (1-f) + v[1] * f;
}
last = v;
}
v = [ element.xrange[1], element.defaulty ];
f = (x - last[0]) / (v[0] - last[0]);
return last[1] * (1-f) + v[1] * f;
}
element.resample = function(samples)
{
var r = [];
var dx = (element.xrange[1] - element.xrange[0]) / samples;
for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
{
r.push( element.getValueAt(i) );
}
return r;
}
element.addValue = function(v)
{
for(var i = 0; i < element.value; i++)
{
var value = element.value[i];
if(value[0] < v[0]) continue;
element.value.splice(i,0,v);
redraw();
return;
}
element.value.push(v);
redraw();
}
//value to canvas
function convert(v)
{
return [ canvas.width * ( (element.xrange[1] - element.xrange[0]) * v[0] + element.xrange[0]),
canvas.height * ((element.yrange[1] - element.yrange[0]) * v[1] + element.yrange[0])];
}
//canvas to value
function unconvert(v)
{
return [(v[0] / canvas.width - element.xrange[0]) / (element.xrange[1] - element.xrange[0]),
(v[1] / canvas.height - element.yrange[0]) / (element.yrange[1] - element.yrange[0])];
}
var selected = -1;
element.redraw = function()
{
var ctx = canvas.getContext("2d");
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.translate(0,canvas.height);
ctx.scale(1,-1);
ctx.fillStyle = element.bgcolor;
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.strokeStyle = element.linecolor;
ctx.beginPath();
//draw line
var pos = convert([element.xrange[0],element.defaulty]);
ctx.moveTo( pos[0], pos[1] );
for(var i in element.value)
{
var value = element.value[i];
pos = convert(value);
ctx.lineTo( pos[0], pos[1] );
}
pos = convert([element.xrange[1],element.defaulty]);
ctx.lineTo( pos[0], pos[1] );
ctx.stroke();
//draw points
for(var i = 0; i < element.value.length; i += 1)
{
var value = element.value[i];
pos = convert(value);
if(selected == i)
ctx.fillStyle = "white";
else
ctx.fillStyle = element.pointscolor;
ctx.beginPath();
ctx.arc( pos[0], pos[1], selected == i ? 4 : 2, 0, Math.PI * 2);
ctx.fill();
}
if(element.show_samples)
{
var samples = element.resample(element.show_samples);
ctx.fillStyle = "#888";
for(var i = 0; i < samples.length; i += 1)
{
var value = [ i * ((element.xrange[1] - element.xrange[0]) / element.show_samples) + element.xrange[0], samples[i] ];
pos = convert(value);
ctx.beginPath();
ctx.arc( pos[0], pos[1], 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
var last_mouse = [0,0];
function onmousedown(evt)
{
$(document).bind("mousemove",onmousemove);
$(document).bind("mouseup",onmouseup);
var rect = canvas.getBoundingClientRect();
var mousex = evt.clientX - rect.left;
var mousey = evt.clientY - rect.top;
selected = computeSelected(mousex,canvas.height-mousey);
if(selected == -1)
{
var v = unconvert([mousex,canvas.height-mousey]);
element.value.push(v);
sortValues();
selected = element.value.indexOf(v);
}
last_mouse = [mousex,mousey];
element.redraw();
evt.preventDefault();
evt.stopPropagation();
}
function onmousemove(evt)
{
var rect = canvas.getBoundingClientRect();
var mousex = evt.clientX - rect.left;
var mousey = evt.clientY - rect.top;
if(mousex < 0) mousex = 0;
else if(mousex > canvas.width) mousex = canvas.width;
if(mousey < 0) mousey = 0;
else if(mousey > canvas.height) mousey = canvas.height;
//dragging to remove
if( selected != -1 && distance( [evt.clientX - rect.left, evt.clientY - rect.top], [mousex,mousey] ) > canvas.height * 0.5 )
{
element.value.splice(selected,1);
onmouseup(evt);
return;
}
var dx = last_mouse[0] - mousex;
var dy = last_mouse[1] - mousey;
var delta = unconvert([-dx,dy]);
if(selected != -1)
{
var minx = element.xrange[0];
var maxx = element.xrange[1];
if(element.no_trespassing)
{
if(selected > 0) minx = element.value[selected-1][0];
if(selected < (element.value.length-1) ) maxx = element.value[selected+1][0];
}
var v = element.value[selected];
v[0] += delta[0];
v[1] += delta[1];
if(v[0] < minx) v[0] = minx;
else if(v[0] > maxx) v[0] = maxx;
if(v[1] < element.yrange[0]) v[1] = element.yrange[0];
else if(v[1] > element.yrange[1]) v[1] = element.yrange[1];
}
sortValues();
element.redraw();
last_mouse[0] = mousex;
last_mouse[1] = mousey;
onchange();
evt.preventDefault();
evt.stopPropagation();
}
function onmouseup(evt)
{
selected = -1;
element.redraw();
$(document).unbind("mousemove",onmousemove);
$(document).unbind("mouseup",onmouseup);
onchange();
evt.preventDefault();
evt.stopPropagation();
}
function onresize(e)
{
canvas.width = $(this).width();
canvas.height = $(this).height();
element.redraw();
}
function onchange()
{
if(options.callback)
options.callback.call(element,element.value);
else
$(element).change();
}
function distance(a,b) { return Math.sqrt( Math.pow(b[0]-a[0],2) + Math.pow(b[1]-a[1],2) ); };
function computeSelected(x,y)
{
var min_dist = 100000;
var max_dist = 8; //pixels
var selected = -1;
for(var i=0; i < element.value.length; i++)
{
var value = element.value[i];
var pos = convert(value);
var dist = distance([x,y],pos);
if(dist < min_dist && dist < max_dist)
{
min_dist = dist;
selected = i;
}
}
return selected;
}
function sortValues()
{
var v = null;
if(selected != -1)
v = element.value[selected];
element.value.sort(function(a,b) { return a[0] > b[0]; });
if(v)
selected = element.value.indexOf(v);
}
element.redraw();
return element;
}
LiteGUI.LineEditor = LineEditor;
})();