example.lfo.jsui.js

Summary

Example usage of lfo.jsui UI elements javascript code. Contains Button, IconButton, and TextButton UI object classes, as well as a generic event handler, and code showing how to use them all to create a fully-functional GUI in Max/MSP/Jitter's JSUI object.

Version: $Id: example.lfo.jsui.js,v 1.4 2006/09/06 10:53:08 evan Exp $


Class Summary
Button  
ExampleGUIHandler  
GUIHandler  
IconButton  
TextButton  

Method Summary
static Array arrayMultiply(<Array> a, <Array> b)
           Multiply 2 arrays of same length.
static void bang()
           Bang == draw
static void draw(<Boolean> forceDraw)
           Draw UI elements to jsui, as necessary.
static void getProp(propName)
           Print a property to outlet (not to be confused with getprop)
static void init()
           Initialize everything.
static void loadbang()
           Set up file paths, mostly for loading icons later.
static void onclick(x,y,button,cmd,shift,capslock,option,ctrl)
           Mouse input function.
static void ondblclick(x,y,button,cmd,shift,capslock,option,ctrl)
           Mouse input function.
static void ondrag(x,y,button,cmd,shift,capslock,option,ctrl)
           Mouse input function.
static void onidle(x,y,button,cmd,shift,capslock,option,ctrl)
           Mouse input function.
static void onidleout(x,y,button,cmd,shift,capslock,option,ctrl)
           Mouse input function.
static void printProp(propName)
           Print a property to Max window
static void setupButtons()
           Button setup functions
static void updateButtons()
           Update Button locations on the screen, for all Buttons.

/**
 * @fileoverview
 *
 * Example usage of lfo.jsui UI elements javascript code.
 * Contains Button, IconButton, and TextButton UI object classes,
 * as well as a generic event handler, and code showing how to use
 * them all to create a fully-functional GUI in Max/MSP/Jitter's JSUI
 * object.
 * @version $Id: example.lfo.jsui.js,v 1.4 2006/09/06 10:53:08 evan Exp $
 *
 */


/**
 * Set number if inlets (jsui method)
 */
inlets = 1;
/**
 * Set number if outlets (jsui method)
 */
outlets = 1;

//sketch.ortho3d();

/**
 * The name to change the patcher window to, if runing as a standalone or app.
 * @type String
 */
var standaloneName = "LFO (lowfrequency.org) lfo.jsui Tester";

/**
 * Whether or not the jsui can be drawn.
 * @type Boolean
 */
var okToDraw = false;
/**
 * Force the jsui to clear and redraw everything.
 * @type Boolean
 */
var forceDraw = false;
/**
 * Report the mouse state out of the outlet
 * @type Boolean
 * @see #onclick
 * @see #ondblclick
 * @see #ondrag
 * @see #onidle
 * @see #onidleout
 */
var reportMouse = true;

/**
 * jsui background color (R,G,B,A).
 * @type Array(4)
 */
var brgb;

var sInfoOverlayColor;
var sInfoTextColor;
var sInfoFontSize;
var sInfoFont;

/**
 * Scale of this jsui instance (x/y) 
 * @type float
 */
var jsuiScale;
/**
 *  Maximum and minimum x coordinates for this jsui (OpenGL).
 * @type Array(2)
 */
var jsuiXBounds;
/**
 *  Maximum and minimum y coordinates for this jsui (OpenGL).
 *  Almost always (1,-1)
 * @type Array(2)
 */
var jsuiYBounds;

/**
 * Whether the jsui needs to clear and redraw everything.
 * @type Boolean
 */
var needsClear = true;

/**
 * File path for this application (if it is an app or standalone)
 * @type String
 */
var appFilePath;
/**
 * File path for the icon image files (named "/icons", relative to app path)
 * @type String
 */
var iconsFilePath;


/**
 * Example Button variables
 */
var rotateCWButton, rotateCCWButton, sillyButton1, sillyButton2;

/**
 * Example Button position variables (each stored separately)
 */
var rotateCWButtonXY, rotateCCWButtonXY, sillyButton1XY, sillyButton2XY;

/**
 * Image file name, for the IconButton.
 * @see #iconsFilePath
 */
var rotateCWButtonImg = "rotateCW.gif";
/**
 * Image file name, for the IconButton.
 * @see #iconsFilePath
 */
var rotateCCWButtonImg = "rotateCCW.gif";


/**
 * These are arrays of buttons in the UI.
 * In a real app, it would be useful to divide you buttons up into "sets"
 * or arrays of Buttons, making it easier to catch maouse events and 
 * re-draw only portions of the UI
 */

var sillyButtons, rotateButtons;


// UI-event handler
var buttonsHandler;

var debug = 0;  // debug level - 1 is most verbose, 2 is major errors, 0 is off

// UI event vars
var clickedButton = "";		//mouse click
var dblclickedButton = "";	//mouse dblclick
var overButton = "";		//mouseover


/******************* patcher state related functions *********/

declareattribute("brgb", null, null, 1);
declareattribute("sInfoOverlayColor", null, null, 1);
declareattribute("sInfoTextColor", null, null, 1);
declareattribute("sInfoFontSize", null, null, 1);
declareattribute("sInfoFont", null, null, 1);

declareattribute("rotateCWButtonXY", null, null, 1);
declareattribute("rotateCCWButtonXY", null, null, 1);
declareattribute("sillyButton1XY", null, null, 1);
declareattribute("sillyButton2XY", null, null, 1);


/**************************************************
 * Utility functions that every jsui js should have
 * @ignore
 */
 
/**
 * Print a property to outlet (not to be confused with getprop)
 */
function getProp(propName)
{
	outlet(0, "prop", propName, eval(propName));
}

/**
 * Print a property to Max window
 */
function printProp(propName)
{
	post(eval(propName+".toString()"));
}

/**
 * Multiply 2 arrays of same length.
 * @param {Array} a
 * @param {Array} b
 * @return New Array with each consecutive element of a multiplied by 
 *  each consecutive element of b  
 * @type Array
 */
function arrayMultiply(a, b)
{
	var c=0;
	dest = new Array(a.length);
	
	for (c=0; c < a.length; c++)
	{
		dest[c] = a[c]*b[c];
	}
	return dest;
}


/*************************************************/

/**
 * Set up file paths, mostly for loading icons later.
 */
function loadbang()
{
	iconsFilePath = "";
	appFilePath = "";
 	
	if (max.isruntime) appFilePath = max.apppath;
	else 
	{
		var tmp = this.patcher.filepath.split("/");

		//if (debug == 1) 
		//post("Filepath: "+  tmp + "\n");
	
		tmp.pop();  //remove patcher name

		for(var c=0; c<tmp.length; c++)
		{
			appFilePath += tmp[c] + "/";
		}
	}
	iconsFilePath = appFilePath + "icons/";

	init();
}


/*************************************************
 * Button setup functions
 *
 * NOTE: there are clearly better ways of doing this.
 * This way is just very straightforward.  This is an example file, after all.
 * For instance, if you wanted to create a row of transport buttons, instead of writing
 * out all of this button code, just make an array of button names ("play", "stop", etc)
 * and have the icon file names be modifications of those names (ex: "play_icn.gif").
 * It would save a lot of time!
 */
 
function setupButtons()
{
	rotateButtons = new Array();

	rotateCWButton = null;
	rotateCWButton = new IconButton("rotateCWButton", sketch, iconsFilePath+rotateCWButtonImg); 
	//rotateCWButton.debug = 1;
	rotateCWButton.setHandler(buttonsHandler);
	rotateButtons.push(rotateCWButton);
	rotateCWButton.setActive(true);

	rotateCCWButton = null;
	rotateCCWButton = new IconButton("rotateCCWButton", sketch, iconsFilePath+rotateCCWButtonImg); 
	rotateCCWButton.setHandler(buttonsHandler);
	rotateButtons.push(rotateCCWButton);
	rotateCCWButton.setActive(true);
	rotateCCWButton.setHidden(false);
	
	for (var c=0; c < rotateButtons.length; c++)
	{
		rotateButtons[c].data = rotateButtons[c].name;
		rotateButtons[c].overmask = [0.9, 0.9, 0., 0.3];
		rotateButtons[c].clickmask = [0., 0., 0.9, 0.3];
	}


	var text_dims = [0,0];

	sillyButtons = new Array();

	sillyButton1 = null;
	sillyButton1 = new TextButton("sillyButton1", sketch, "silliness"); 
	//sillyButton1.debug = 1;

	sillyButton1.sketcher.font(sillyButton1.font);
	sillyButton1.sketcher.fontsize(sillyButton1.fontSize);
	text_dims = sillyButton1.sketcher.gettextinfo(sillyButton1.text);
	sillyButton1.setSize(text_dims[0]+4, text_dims[1]+8);
	
	sillyButton1.setHandler(buttonsHandler);
	sillyButtons.push(sillyButton1);
	sillyButton1.setActive(true);
	sillyButton1.setHidden(false);

	sillyButton2 = null;
	sillyButton2 = new TextButton("sillyButton2", sketch, "very silly"); 
	sillyButton2.sketcher.font(sillyButton2.font);
	sillyButton2.sketcher.fontsize(sillyButton2.fontSize);
	text_dims = sillyButton2.sketcher.gettextinfo(sillyButton2.text);
	sillyButton2.setSize(text_dims[0]+4, text_dims[1]+6);

	sillyButton2.setHandler(buttonsHandler);
	sillyButtons.push(sillyButton2);
	sillyButton2.setActive(true);
	
	for (var c=0; c < sillyButtons.length; c++)
	{
		sillyButtons[c].data = sillyButtons[c].name;
		sillyButtons[c].overmask = [0.9, 0.9, 0., 0.3];
		sillyButtons[c].clickmask = [0., 0., 0.9, 0.3];
	}
}

/**
 * Update Button locations on the screen, for all Buttons.
 * Setting their screenLocation forces them to redraw.
 */
function updateButtons()
{
	rotateCCWButton.setScreenLocation(rotateCCWButtonXY[0],rotateCCWButtonXY[1]);
	rotateCWButton.setScreenLocation(rotateCWButtonXY[0],rotateCWButtonXY[1]);
	sillyButton1.setScreenLocation(sillyButton1XY[0],sillyButton1XY[1]);
	sillyButton2.setScreenLocation(sillyButton2XY[0],sillyButton2XY[1]);
	needsClear = true;
}


/**
 * Initialize everything.
 */
function init()
{

	okToDraw = false;
	
	if (!brgb) brgb = [0.25, 0.25, 0.25];
	if (!sInfoOverlayColor) sInfoOverlayColor = [0.8, 0.8, 0.8, 0.3];
	if (!sInfoTextColor) sInfoTextColor = [0.9, 0.9, 0.9, 0.7];
	if (!sInfoFontSize) sInfoFontSize = 10;
	if (!sInfoFont) sInfoFont = "Helvetica";

	if (!rotateCCWButtonXY) rotateCCWButtonXY =[0,0];
	if (!rotateCWButtonXY) rotateCWButtonXY =[0,0];	
	if (!sillyButton1XY) sillyButton1XY =[0,0];
	if (!sillyButton2XY) sillyButton2XY =[0,0];
	
	rotateButtons = null;
	rotateButtons = new Array();

	sillyButtons = null;
	sillyButtons = new Array();
	
	if (debug == 1) post("Sketch size: " + sketch.size);

	jsuiScale = [2/sketch.size[0], 2/sketch.size[1]];
	jsuiXBounds = [-sketch.size[0]/sketch.size[1], sketch.size[0]/sketch.size[1]];
	jsuiYBounds = [-1, 1];

	buttonsHandler = new ExampleGUIHandler();
	
	setupButtons();
	updateButtons();
	
	okToDraw = true;
	needsClear = true;
	draw();
	
	// this is for standalone
	if (max.isruntime) this.patcher.wind.title = standaloneName;
}

/**
 * Bang == draw
 * @see #draw
 */
function bang()
{
	draw();
}

/**
 * Draw UI elements to jsui, as necessary.  Calls draw() method on each UI element.
 * @param {Boolean} forceDraw Optional, but if true, clear jsui and redraw everything.
 */
function draw(forceDraw)
{

	var force = false;

	if (forceDraw || needsClear)
	{
		force = true;
	
		// erase background
		sketch.glclearcolor(brgb);
		sketch.glclear();
		needsClear = false;
/*
		sketch.glcolor(sInfoOverlayColor);
		sketch.quad(jsuiXBounds[0], -0.2, 0,
			 jsuiXBounds[0], 0, 0,
			 jsuiXBounds[1]-screensButtonsWidth, 0, 0,
			 jsuiXBounds[1]-screensButtonsWidth, -0.2, 0);
		sketch.quad(jsuiXBounds[1]-screensButtonsWidth, -0.2, 0,
			 jsuiXBounds[1]-screensButtonsWidth, 0.2, 0,
			 jsuiXBounds[1], 0.2, 0,
			 jsuiXBounds[1], -0.2, 0);
*/
	}
	if (okToDraw)
	{
		outlet(0, "draw");
		// draw buttons
		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].draw(force);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].draw(force);
		}

		// draw surfaces/screens info overlay in middle
		//drawSInfoOverlay();
	}
	refresh();
}

/**
 * Recalculate scale and x-boundaries when jsui object is resized in Max.
 * @param {int} w width (pixels)
 * @param {int} h height (pixels)
 * @private
 */
function onresize(w,h)
{

	jsuiScale = [2/sketch.size[0], 2/sketch.size[1]];
	jsuiXBounds = [-sketch.size[0]/sketch.size[1], sketch.size[0]/sketch.size[1]];
	
	draw(true);
	refresh();
}
/**
 * @ignore
 */
onresize.local = 1; //private



/************* Mouse Input Functions ********************/

/**
 * Mouse input function.  Catches mouse idle event.
 * If reportMouse is set, outputs mouse state out first inlet.
 */

function onidle (x,y,button,cmd,shift,capslock,option,ctrl)
{
	if (reportMouse)
		outlet(0,"mouse",x,y,button,cmd,shift,capslock,option,ctrl);

		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].onidle (x,y,button,cmd,shift,capslock,option,ctrl);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].onidle (x,y,button,cmd,shift,capslock,option,ctrl);
		}
}

/**
 * Mouse input function.  Catches mouse idle event.
 * If reportMouse is set, outputs mouse state out first inlet.
 */
function onidleout (x,y,button,cmd,shift,capslock,option,ctrl)
{
		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].onidleout (x,y,button,cmd,shift,capslock,option,ctrl);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].onidleout (x,y,button,cmd,shift,capslock,option,ctrl);
		}
}

/**
 * Mouse input function.  Catches mouse idle event.
 * If reportMouse is set, outputs mouse state out first inlet.
 */
function onclick (x,y,button,cmd,shift,capslock,option,ctrl)
{
	if (reportMouse)
		outlet(0,"mouse",x,y,button,cmd,shift,capslock,option,ctrl);

		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].onclick (x,y,button,cmd,shift,capslock,option,ctrl);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].onclick (x,y,button,cmd,shift,capslock,option,ctrl);
		}
}

/**
 * Mouse input function.  Catches mouse idle event.
 * If reportMouse is set, outputs mouse state out first inlet.
 */
function ondblclick (x,y,button,cmd,shift,capslock,option,ctrl)
{
		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].ondblclick (x,y,button,cmd,shift,capslock,option,ctrl);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].ondblclick (x,y,button,cmd,shift,capslock,option,ctrl);
		}
}

/**
 * Mouse input function.  Catches mouse idle event.
 * If reportMouse is set, outputs mouse state out first inlet.
 */
function ondrag (x,y,button,cmd,shift,capslock,option,ctrl)
{
	if (reportMouse)
		outlet(0,"mouse",x,y,button,cmd,shift,capslock,option,ctrl);

		for (var c=0; c < rotateButtons.length; c++)
		{
			rotateButtons[c].ondrag (x,y,button,cmd,shift,capslock,option,ctrl);
		}
		for (var c=0; c < sillyButtons.length; c++)
		{
			sillyButtons[c].ondrag (x,y,button,cmd,shift,capslock,option,ctrl);
		}
}



/*********************************************************************
 lfo.jsui.js
*********************************************************************/


/**
 * Simple Button classes for GUI events.
 * Reports it's data to a listener object with the functions
 * clicked, mouseOver, mouseOut defined.
 * Buttons are drawn around a center point (location).
 * @param {String} name This will be the name of the button (a way of identifying it)
 * @param {Object} sketcher The sketch object that will draw the button, associated with this jsui
 *
 * @constructor
 * $Id: example.lfo.jsui.js,v 1.4 2006/09/06 10:53:08 evan Exp $
 */

function Button(name, sketcher)
{
	if (name) this.name = name;
	else  this.name = "button";
	
	/**
	 * The data (object) to broadcast to this Button's handler when clicked.
	 */
	this.data = null;

	/**
	 * A reference to an OpenGL drawing context (sketch object)
	 * @type sketch
	 */
	if (sketcher) this.sketcher = sketcher;
	else this.sketcher = new Sketch(2,2);
	/**
	 * A string identifier for this class.
	 * @type String
	 */
	this.className = "Button";
	/**
	 * Reference to a handler object to broadcast UI events to
	 */
	this.handler = null;
	/**
	 * Whether the mouse was positioned over this button last time we checked.
	 * @type Boolean
	 */
	this.mouseWasOver = false;
	/**
	 * Whether this Button was clicked.
	 * @type Boolean
	 */
	this.clicked = false;
	/**
	 * Location of this Button in screen coordinates (pixels) (x,y,z)
	 * (Top left corner of drawn Button)
 	 * @type Array(3)
	 * @see #setScreenLocation
	 */
	this.screenLocation = [0,0,0];
	/**
	 * Location of this Button in OpenGL world coordinates (x,y,z)
	 * (Center of drawn Button)
	 * @type Array(3)
	 * @see #setWorldLocation
	 */
	this.worldLocation = [0,0,1];
	/**
	 * Size of this Button in screen coordinates (pixels) (x,y)
  	 * @type Array(2)
	 * @see #setSize
	 */
	this.size = new Array(2);
	/**
	 * Scale of this Button in OpenGL world coordinates (x,y)
  	 * @type Array(2)
	 * @see #setScale
	 */
	this.scale = new Array(0.1, 0.05);
	/**
	 * Base OpenGL color of this Button - [R,G,B,A]
  	 * @type Array(4)
	 */
	this.color = new Array(0.2, 0.2, 0.2, 1.);
	/**
	 * Base OpenGL hitmask (mouse click overlay) color of this Button - [R,G,B,A]
 	 * @type Array(4)
	 */
	this.hitmask = new Array(0., 0., 0.5, 0.3);
	/**
	 * Base OpenGL overmask (mouse over overlay) color of this Button - [R,G,B,A]
	 * @type Array(4)
	 */
	this.overmask = new Array(0.5, 0.5, 0.9, 0.2);
	/**
	 * Base OpenGL inactive state mask color of this Button - [R,G,B,A]
	 * @see #setActive
	 * @type Array(4)
	 */
	this.inactivemask = new Array(0.7, 0.7, 0.7, 0.3);
	/**
	 * Whether or not this Button needs to be re-drawn fully when draw() is called again.
  	 * @type Array(4)
	 * @private
	 */
	this.needsDraw = true;
	/**
	 * If non-zero, posts verbose debugging information to the Max window.
	 * @type Boolean
	 */	
	this.debug = 0;	//spit out debugging information = levels are 0,1,2 (0 is off, 2 is least verbose)
	/**
	 * How many frames (calls to draw()) elapse before the clicked state is reset.
	 * @type int
	 * @see #draw
	 */
	this.clickedHoldMax = 4;	//how many drawing cycles to stay "clicked" for
	/**
	 * Current number of frames (calls to draw()) that have elapsed while clicked state was true.
	 * @type int
	 * @see #draw
	 */
	this.clickedCount = 0;
	/**
	 * Whether or not this Button listens to GUI events (active state, deactivated state)
	 * @type Boolean
	 * @see #draw
	 * @see #hitTest
	 */
	this.active = true;
	/**
	 * Whether or not this Button should be drawn.
	 * @type Boolean
	 * @see #draw
	 */
	this.hidden = false;
	/**
	 * Whether this button holds after clicking until it's either clicked again or reset.
	 * @type Boolean
	 * @see #draw
	 */
	this.toggle = false;

	this.setScale(0.1, 0.05);
	this.setScreenLocation(0,0,0);
}

/**
 * Sets this Button as active (receives mouse events)
 * @param {Boolean} state True if the Button should listen to events, false if it should ignore them.
 */
Button.prototype.setActive = function(state)
{
	this.active = state;
	this.needsDraw = true;
}
/**
 * Get this Button's active state (if it is receiving mouse events)
 * @return true (listening to events) or false (ignoring events) 
 * @type Boolean
 */
Button.prototype.getActive = function()
{
	return this.active;
}
/**
 * Sets this Button as hidden (not drawn) or not hidden (drawn)
 * @param {Boolean} state True if the Button will not be drawn
 */
Button.prototype.setHidden = function(state)
{
	this.hidden = state;
}
/**
 * Get this Button's hidden state (if it should be drawn)
 * @return true (not drawn) or false (drawn) 
 * @type Boolean
 */
Button.prototype.getHidden = function()
{
	return this.hidden;
}
/**
 * Sets the scale (as in OpenGL, the scale is 2x the width of the drawn button)
 * @param {int} sx x scale
 * @param {int} sy y scale
 */
Button.prototype.setScale = function(sx,sy)
{
	this.scale = new Array(sx,sy);
	
	//pretend we're drawing this at 0,0 to get screen-size
	var w0 = this.sketcher.worldtoscreen(0, 0, 0);
	var w1 = this.sketcher.worldtoscreen(2*sx, 0, 0);
	
	this.size[0] = w1[0] - w0[0];
	
	var h0 = this.sketcher.worldtoscreen(0, 0, 0);
	var h1 = this.sketcher.worldtoscreen(0,2*sy, 0);

	this.size[1] = h0[1] - h1[1];

	this.onresize();
}
/**
 * Get this Button's scale.
 * @return Array of [x-scale, y-scale]
 * @type Array(2)
 */
Button.prototype.getScale = function()
{
	return this.scale;
}
/**
 * Sets the size of the button (width, height in pixels)
 * @param {int} sx x size (pixels)
 * @param {int} sy y size (pixels)
 */
Button.prototype.setSize = function(sx,sy)
{
	this.size = new Array(sx,sy);
	
	//pretend we're drawing this at 0,0 to get world-size
	var w0 = this.sketcher.screentoworld(0, 0, 0);
	var w1 = this.sketcher.screentoworld(sx*0.5, 0, 0);
	
	this.scale[0] = w1[0] - w0[0];
	
	var h0 = this.sketcher.screentoworld(0, 0, 0);
	var h1 = this.sketcher.screentoworld(0,sy*0.5, 0);

	this.scale[1] = h0[1] - h1[1];

	this.onresize();
}
/**
 * Get this Button's size (width, height) in pixels
 * @return Array of [x-size, y-size]
 * @type Array(2)
 */
Button.prototype.getSize = function()
{
	return this.size;
}

/**
 * Set the location of this button in world coords (OpenGL, for sketch)
 * @param {float} x x coordinate
 * @param {float} y y coordinate
 * @param {float} z z coordinate
 */
Button.prototype.setWorldLocation = function(x,y,z)
{
	this.worldLocation = [x,y,z];

	// sketch draws a plane centered around location, 
	// but screen co-ords represent the bounding rectangle
	// need to represent bounding box here
	var tmp = new Array(this.worldLocation.length);
	tmp[0] = this.worldLocation[0] - this.scale[0];
	tmp[1] = this.worldLocation[1] + this.scale[1];
	tmp[2] = this.worldLocation[2];	
	
	this.screenLocation = this.sketcher.worldtoscreen(tmp);

	this.needsDraw = true;
}

/**
 * Get the location of this button in world coords (OpenGL, for sketch)
 * @return 3-member Array of floats [x,y,z]
 * @type Array(3)
 */
Button.prototype.getWorldLocation = function()
{
	return this.worldLocation;
}

/**
 * Set the location of this button in screen coordinates (pixels)
 * @param {int} x x coordinate
 * @param {int} y y coordinate
 * @param {int} z z coordinate (optional)
 */
Button.prototype.setScreenLocation = function(x,y,z)
{
	this.screenLocation[0] = x;
	this.screenLocation[1] = y;
	if ((z != null) || (z != undefined)) this.screenLocation[2] = z;
	else z=0;

	var tmp = this.sketcher.screentoworld(this.screenLocation);

	// sketch draws a plane centered around location, 
	// but screen co-ords represent the bounding rectangle
	// need to represent center here
	tmp[0] += this.scale[0];
	tmp[1] -= this.scale[1];
	 
	this.worldLocation = tmp;
	
	if (this.debug == 1) post("screen location: " + this.screenLocation + "\n" + "world location: " + this.worldLocation + "\n");

	this.needsDraw = true;
}

/**
 * Get the location of this button in screen coordinates (pixels)
 * @return 3-member Array of ints [x,y,z]
 * @type Array(3)
 */
Button.prototype.getScreenLocation = function()
{
	return (this.screenLocation);
}


/**
 * Mouse click event handler
 */
Button.prototype.onclick = function(x,y,button,cmd,shift,capslock,option,ctrl)
{
	if (this.hitTest(x,y)) 
	{
		if (this.debug == 1) post(this.name + " button: " + button + "\n");
		if (this.toggle && this.clicked)
			this.clicked = false;	//unclick
		else this.clicked = true;
		
		this.needsDraw = true;
 
		if(this.handler) this.handler.clicked(this.data, x,y,button,cmd,shift,capslock,option,ctrl);
	}
	// something needs to externally reset this
	// else this.clicked = false; 
}


/**
 * Manual/code-triggered mouse click event handler.
 * @param {Boolean} showPush Whether or not to show the push when drawing the Button to screen.  
 */
Button.prototype.push = function(showPush)
{
	if (this.debug == 1) post(this.name + "button (push): " + button + "\n");
	if (this.toggle && this.clicked)
		this.clicked = false;	//unclick
	else this.clicked = true;
	if (showPush)
		this.needsDraw = true;
 		
	if(this.handler) this.handler.clicked(this.data, -1,-1,0,0,0,0,0,0);
}


/**
 * Mouse idle event handler
 */
Button.prototype.onidle = function (x,y,button,cmd,shift,capslock,option,ctrl)
{
	if (this.hitTest(x,y)) 
	{
		this.mouseWasOver = true;
		if (this.debug == 1) post ("MOUSEOver " + this.name + "\n");
		this.needsDraw = true;
		if(this.handler) this.handler.mouseover(this.data, x,y,button,cmd,shift,capslock,option,ctrl);

	} else {
		// only report changes

		//clear mouseOvers
		if (this.mouseWasOver)
		{
			this.mouseWasOver = false;	
			this.needsDraw = true;
			if(this.handler) this.handler.mouseout(this.data, x,y,button,cmd,shift,capslock,option,ctrl);
		}
		if (this.debug == 1) post("MOUSEOut " + this.name + "\n");
	}

}


/**
 * Mouse double-click event handler
 */
Button.prototype.ondblclick  = function(x, y, button, mod1, shift, caps, opt, mod2)
{
	if (this.hitTest(x,y)) if(this.handler) this.handler.dblclicked(this.data, x, y, button, mod1, shift, caps, opt, mod2);
}


/**
 * Mouse drag event handler
 */
Button.prototype.ondrag  = function(x, y, button, mod1, shift, caps, opt, mod2)
{}

/**
 * Mouse idleout event handler
 */
Button.prototype.onidleout  = function(x, y, button, mod1, shift, caps, opt, mod2)
{
	//clear mouseOvers
	if (this.mouseWasOver)
	{
		this.mouseWasOver = false;	
		this.needsDraw = true;
		if(this.handler) this.handler.idleout(this.data, x, y, button, mod1, shift, caps, opt, mod2);
	}
	if (this.debug == 1) post("MOUSEOut " + this.name + "\n");
}

/**
 * Actions to perform when the Button is resized.  Can be overloaded.
 */
Button.prototype.onresize  = function()
{
	this.needsDraw = true;
}

/**
 * Test if an x,y coordinate falls within the Button's boundaries.
 * @param {int} x x-coordinate
 * @param {int} y y-coordinate
 */
Button.prototype.hitTest = function(x,y)
{
	var over = false;

	// only test if the button is active
	if (this.active)
	{
		//note: screen location coords are with respect to top left (0,0)	
		if ( ((x > this.screenLocation[0]) && (x < (this.screenLocation[0]+this.size[0]))) &&
			 ((y > this.screenLocation[1]) && (y < (this.screenLocation[1]+this.size[1]))) )
			over = true;
	
		if (this.debug == 1) {
			var os = "false";
			if (over) os = "true";
			post("hitTest " + over + " (" + this.name + ") sl[0]= " + this.screenLocation[0] + "\n" +
			"size[0]= " + this.size[0] +"\n" + "mx=" + x + "\n" +
			"sl[1]= " + this.screenLocation[1] + "\n" +
			"size[1]= " + this.size[1] +"\n" + "mx=" + x + "\n");
		}
	}	
	return over;
}

/**
 * Set reference to handler obect for Button events
 * @param {Object} hobj Handler object that will receive this Button's events.
 */
Button.prototype.setHandler = function(hobj)
{
	this.handler = hobj;
}

/**
 * Get reference tohandler obect for Button events
 * @return Reference to handler object that will receive this Button's events.
 * @type Object
 */
Button.prototype.getHandler = function()
{
	return this.handler;
}

/**
 * Set the clicked state of this Button, update GUI.  Usually, this is only called 
 * internally.
 *
 * A Button's clicked state is set to true after it is pressed, and remains 
 * true for a number of drawing frames (this.clickedCount) while the clicked overlay
 * is drawn, and then it reset back to false.
 * @param {Boolean} state The new internal clicked state of the Button, true or false
 * 
 */
Button.prototype.setClicked = function(state)
{
	this.clicked = state;
	this.needsDraw = true;
}

/**
 * Main OpenGL drawing routine for drawing this Button to the screen, using its 
 * internal reference to the jsui "sketch" object.  The "base" is drawn first {@link #drawBase}, then 
 * a mouseover overlay {@link #drawOverOverlay} or inactive overlay {@link #drawInactiveOverOverlay}.
 * This function is designed to be flexible so it does NOT need to be overloaded by child classes.
 *
 * Normally, the Button will only re-draw if it has to (if an event has taken place
 * that affects the onscreen appearance of the Button, and if it is NOT hidden).
 * 
 * @param {Boolean} force Force the drawing of the Button.
 * 
 */
Button.prototype.draw = function(force)
{
	if (!this.hidden)
	if (force || this.needsDraw)
	with (this.sketcher)
	{
		this.needsDraw = false;
	
		gldisable("blend");
//		glpushmatrix();

		this.drawBase();

		glenable("blend");
		glblendfunc("src_alpha","one_minus_src_alpha");
		
		if (this.active)
		{
			if (this.clicked)
			{
				this.drawClickedOverlay();
				
				if (!this.toggle)
					{
					// reset clicked
					this.clickedCount++;
					// still needs to be drawn
					this.needsDraw = true;

					if (this.clickedCount >= this.clickedHoldMax)
					{
						this.setClicked(false);
						this.clickedCount = 0;
					}
				}
	
			} else if (this.mouseWasOver)
			{
				this.drawOverOverlay();
			}
		} else {
			this.drawInactiveOverlay();
		}
//		glpopmatrix();
	}

}

/**
 * Draw only basic button elements.
 * This function is designed to be be overloaded by child classes for Buttons of
 * different appearances.
 */
Button.prototype.drawBase = function()
{
	with (this.sketcher)
	{
		glcolor(this.color);
		moveto(this.worldLocation);
		plane(this.scale);
	}
}

/**
 * Draw blended overlay showing button was clicked.
 */
 Button.prototype.drawClickedOverlay = function()
{
	with (this.sketcher)
	{
		glcolor(this.hitmask);
		moveto(this.worldLocation);
		plane(this.scale);
	}
}

/**
 * Draw blended overlay showing mouse is over Button.
 */
Button.prototype.drawOverOverlay = function()
{
	with (this.sketcher)
	{
		glcolor(this.overmask);
		moveto(this.worldLocation);
		plane(this.scale);
	}
}

/**
 * Draw blended overlay showing Button is not receiving UI events (not active).
 */
Button.prototype.drawInactiveOverlay = function()
{
	with (this.sketcher)
	{
		glcolor(this.inactivemask);
		moveto(this.worldLocation);
		plane(this.scale);
	}
}

/**
 * Return string representation of this object
 * @return String of data about this object
 * @type String
 */
Button.prototype.toString = function()
{
	var outstring = ":::Button:::\n";
	with (this)
	{
		outstring += "Button name: " + name + "\n";
		outstring += "Button class: " + className + "\n";

		if (data)
		outstring += "Button data: " + data + "\n";
		if (handler)
		outstring += "Button handler class: " + handler.className + "\n";
		
		if (sketcher)
		outstring += "Button sketcher (" + sketcher + ")\n";
		outstring += "Button active: " + active + "\n";
		outstring += "Button hidden: " + hidden + "\n";
		outstring += "\n";
		outstring += "Button screen location: " + screenLocation + "\n";
		outstring += "Button world location: " + worldLocation + "\n";
		outstring += "Button world scale: " + scale + "\n";
		outstring += "Button screen size: " + size + "\n";
		outstring += "\n";
		outstring += "Button color: " + color + "\n";
		outstring += "Button hitcolor: " + hitmask + "\n";
		outstring += "Button overcolor: " + overmask + "\n";
		outstring += "Button inactivecolor: " + inactivemask + "\n";		
	}	
	return outstring;

}

/**
 * Returns a new clone (a new Button object exactly like this one)
 * @return Button clone
 * @type Button
 */
Button.prototype.clone = function()
{
	var nbutton = new Button(this.name, this.sketcher);
	if (typeof(this.data) == "object")
	{
		nbutton.data = new Array(this.data.length);
		for (var i=0; i<this.data.length;i++) nbutton.data[i] = this.data[i];
	}
	else nbutton.data = this.data;
	
	nbutton.className = this.className;
	nbutton.handler =  this.handler;

	nbutton.color        = [this.color[0],this.color[1],this.color[2],this.color[3]];
	nbutton.hitmask      = [this.hitmask[0],this.hitmask[1],this.hitmask[2],this.hitmask[3]];
	nbutton.overmask     = [this.overmask[0],this.overmask[1],this.overmask[2],this.overmask[3]];
	nbutton.inactivemask = [this.inactivemask[0],this.inactivemask[1],this.inactivemask[2],this.inactivemask[3]];
	
	nbutton.clickedHoldMax = this.clickedHoldMax;
	nbutton.clickedCount = 0;
	
	nbutton.active = this.active;
	nbutton.hidden = this.hidden;

	nbutton.setScale(this.scale[0], this.scale[1]);
	nbutton.setScreenLocation(this.screenLocation[0], this.screenLocation[1], this.screenLocation[2]);
	
	return nbutton;
}


/**
 * Simple GUI Button subclass with picture icon.
 * Inherits from button parent class.  Square by default.
 * @constructor
 */

function IconButton(name, sketcher, iconFile)
{
	/**
	 * @private
	 */
	this.parent = Button;
	this.parent(name, sketcher);
	/**
	 * Name of the class: "IconButton"
	 * @final
	 * @type String
	 * @see Button#className
	 */
	this.className = "IconButton";
	/**
	 * R,G,B,A color of this Button's base (will tint image).
	 * Base color is by default all white [1,1,1,1].
	 * @type Array(4)
	 */
	this.color = [1,1,1,1];
	/**
	 * File name of an image file for this Button's icon.
	 * @type String
	 */
	this.iconFile = iconFile || "";
	if (this.iconFile != "")
	{
		/**
		 * Image object holding the loaded iconFile's data.
		 * @type Image
		 * @private
		 */
		this.iconImage = new Image(this.iconFile);
		this.setSize(this.iconImage.size[0], this.iconImage.size[1]);
	}
	else this.iconImage = null;
}

// inherit from Button class
IconButton.prototype = new Button;

/**
 * Loads an icon image file, returns true if success, false otherwise.
 * @return true if success, false otherwise.
 * @type Boolean
 */
IconButton.prototype.loadIcon = function(iconFile)
{
	var return_val = false;
	
	if (iconFile) this.iconFile = iconFile;
	if (this.iconFile != "")
	{
		this.iconImage = new Image(this.iconFile);
		return_val = true;
	}
	else {
		if (this.debug > 0) post("IconButton ("+this.name+") called loadIcon with no icon filename set\n");
		this.iconImage = null;
		return_val = true;
		// set button size properties
		this.setSize(this.iconImage.size[0], this.iconImage.size[1]);
	}

	this.needsDraw = true;

	return return_val;
}

/**
 * Overloaded drawBase function from Button that draws only the icon image.
 */
IconButton.prototype.drawBase = function()
{
	this.sketcher.glcolor(this.color);
	this.sketcher.copypixels(this.iconImage, this.screenLocation[0],this.screenLocation[1]);
}


/**
 * Simple GUI Button subclass with text label.
 * Inherits from button parent class.  It's square by default.
 * @constructor
 */
function TextButton(name, sketcher, text)
{
	/**
	 * @private
	 */
	this.parent = Button;
	this.parent(name, sketcher);
	/**
	 * Name of the class: "TextButton"
	 * @final
	 * @type String
	 * @see Button#className
	 */
	this.className = "TextButton";
	/**
	 * R,G,B,A color of this Button's text.
	 * Text color is by default solid black [0,0,0,1].
	 * @type Array(4)
	 */
	this.textColor = [0,0,0,1];
	/**
	 * Font name for text.
	 * @type String
	 */
	this.font = "Arial";
	/**
	 * Font size (points) for text.
	 * @type float
	 */
	this.fontSize = 10;	//points
	/**
	 * Text to display across Button.
	 * @type String
	 */
	this.text = text || "button";
}

// inherit from Button class
TextButton.prototype = new Button;

/**
 * Set this Button's text string.
 * @param {String} text text string to display across Button.
 * @param {String} font Font name for text.
 * @param {float} fontSize Font size in points.
 */
TextButton.prototype.setText = function(text, font, fontSize)
{
	this.text = text;
	if (font) this.font = font;
	if (fontSize) this.fontSize = fontSize;

	this.needsDraw = true;

}
/**
 * Get this Button's text string.
 * @return text string
 * @type String
 */
TextButton.prototype.getText = function()
{
	return this.text;
}

/**
 * Overloaded drawBase function from Button that draws a plain rectangle and then text.
 */
TextButton.prototype.drawBase = function()
{
	with (this.sketcher)
	{
		glcolor(this.color);
		moveto(this.worldLocation);
		plane(this.scale);
		// move to top left corner
		move(-this.scale[0]+0.02,-0.02);
		font(this.font);
		fontsize(this.fontSize);
		glcolor(this.textColor);
		text(this.text);
		moveto(this.worldLocation);
	}
}



/**
 * Simple GUI-related events handler interface class for GUI events.
 * Handles calls for associated GUILIstener.
 * @constructor
 */
function GUIHandler()
{
	/**
	 * Name of the class: "GUIHandler"
	 * @final
	 * @type String
	 */
	this.className = "GUIHandler";
}

GUIHandler.prototype.clicked = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}
GUIHandler.prototype.dblclicked = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}
GUIHandler.prototype.dragged = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}
GUIHandler.prototype.mouseover = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}
GUIHandler.prototype.mouseout = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}
GUIHandler.prototype.idleout  = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{}


/*********************************************************************
 end lfo.jsui.js
*********************************************************************/


/**
 * Simple GUI-related events handler class for GUI events.
 * Receives events from UI elements and simply outputs them out the first outlet of the jsui.
 * @constructor
 * @base GUIHandler
 */
function ExampleGUIHandler()
{
	/**
	 * Name of the class: "ExampleGUIHandler"
	 * @final
	 * @type String
	 * @see GUIHandler#className
	 */
	this.className = "SillyGUIHandler";
	/**
	 * @private
	 */
	this.parent = GUIHandler;
}

ExampleGUIHandler.prototype = new GUIHandler;

ExampleGUIHandler.prototype.clicked = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{
	outlet(0,"clicked", data,x,y,button,cmd,shift,capslock,option,ctrl);
	clickedButton = data;
}
ExampleGUIHandler.prototype.dblclicked = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{
	outlet(0,"dblclicked", data,x,y,button,cmd,shift,capslock,option,ctrl);
	dblclickedButton = data;
}
ExampleGUIHandler.prototype.dragged = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{
	outlet(0,"dragged", data,x,y,button,cmd,shift,capslock,option,ctrl);
	draggedButton = data;
}
ExampleGUIHandler.prototype.mouseover = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{
	outlet(0,"over", data,x,y,button,cmd,shift,capslock,option,ctrl);
	overButton = data;
}
ExampleGUIHandler.prototype.mouseout = function(data,x,y,button,cmd,shift,capslock,option,ctrl)
{
	outlet(0,"over", data,x,y,button,cmd,shift,capslock,option,ctrl);
	overButton = "";
}
ExampleGUIHandler.prototype.idleout  = function(data,x, y, button, mod1, shift, caps, opt, mod2)
{
	overButton = "";
}


Documentation generated by JSDoc on Wed Sep 6 11:55:06 2006