/**#Interface
A singleton object holding all widget constructors and a couple of other methods / properties. It is automatically created as soon as interface.js is loaded.
**/
/**###Interface.extend : method
This method deep copies all the properties and methods of one object to another.
param **destination** Object. The object that properties and methods will be inserted into.
param **source** Object. The object providing the properties and methods for copying.
**/
/**###Interface.mouseDown : property
Boolean. This property tells whether the left mouse button (in non-touch browsers) is currently pressed.
**/
/**###Interface.useTouch : property
Boolean. Whether or not a touch UI browser is being used.
**/
/**###Interface.isAndroid : property
Boolean. Whether or not the browser is running under Android. This is used to determine the range of accelerometer values generated.
**/
var Interface = {
extend : function(destination, source) {
for (var property in source) {
var keys = property.split(".");
if(source[property] instanceof Array && source[property].length < 100) { // don't copy large array buffers
destination[property] = source[property].slice(0);
} else {
destination[property] = source[property];
}
}
return destination;
},
isAndroid : (function() {
var ua = navigator.userAgent.toLowerCase();
return ua.indexOf("android") > -1;
})(),
keyCodeToChar : {8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause/Break",20:"Caps Lock",27:"Esc",32:"Space",33:"Page Up",34:"Page Down",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",45:"Insert",46:"Delete",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",91:"Windows",93:"Right Click",96:"Numpad 0",97:"Numpad 1",98:"Numpad 2",99:"Numpad 3",100:"Numpad 4",101:"Numpad 5",102:"Numpad 6",103:"Numpad 7",104:"Numpad 8",105:"Numpad 9",106:"Numpad *",107:"Numpad +",109:"Numpad -",110:"Numpad .",111:"Numpad /",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Num Lock",145:"Scroll Lock",182:"My Computer",183:"My Calculator",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},
panels : [],
mouseDown : false,
useTouch : 'ontouchstart' in document.documentElement,
widgets : [],
};
Interface.Presets = {
dictionary : typeof localStorage.interfacejs === 'undefined' ? {} : JSON.parse( localStorage.interfacejs ),
save : function(name) {
var preset = [];
for(var h = 0; h < Interface.panels.length; h++) {
var panel = Interface.panels[h];
preset[h] = [];
for(var i = 0; i < panel.children.length; i++) {
var widget = panel.children[i];
if(typeof widget.children === 'object') {
var children = [];
for(var j = 0; j < widget.children.length; j++) {
children[j] = widget.children[j].value;
}
preset[h][i] = children;
}else{
preset[h][i] = widget.value;
}
}
this.dictionary[ name ] = preset;
localStorage.interfacejs = JSON.stringify( this.dictionary );
}
},
load : function(name) {
var preset = this.dictionary[ name ];
for(var h = 0; h < Interface.panels.length; h++) {
var panel = Interface.panels[h];
for(var i = 0; i < panel.children.length; i++) {
var widget = panel.children[i];
if(typeof widget.children === 'object') {
for(var j = 0; j < widget.children.length; j++) {
widget.children[j].setValue( preset[h][i][j] );
}
}else{
widget.setValue( preset[h][i] );
}
}
}
},
list : function() {
return Object.keys( this.dictionary );
},
};
/**#Interface.Panel - Widget
A panel is a container for on-screen widgets. There can be multiple panels in a HTML page. Panels are the starting point for event processing in Interface.js.
**/
/**###Interface.Panel.children : property
Array. An array of all widgets displayed in the panel
**/
/**###Interface.Panel.shouldDraw : property
Boolean. Whenever the panel refreshes itself it will redraw widgets found in this array.
**/
/**###Interface.Panel.fps : property
Number. The number of times the panel should refresh itself per second.
**/
/**###Interface.Panel.useRelativeSizesAndPositions : property
Boolean. This determines whether widgets in the panel uses sizes/positions relative to the size of the panel or use absolute pixel coordinates.
**/
/**###Interface.Panel.container : property
HTMLElement. The HTMLElement (such as a div tag) containing the Panel.
**/
/**###Interface.Panel.canvas : property
HTMLElement. The canvas element that the Panel draws onto. This element is created when the panel is initialized.
**/
/**###Interface.Panel.touchEvent : method
The starting point for on-screen all touch event handling in a Panel. This method distributes events to all child widgets.
param **event** HTML Touch Event Object.
**/
/**###Interface.Panel.mouseEvent : method
The starting point for on-screen all mouse event handling in a Panel. This method distributes events to all child widgets.
param **event** HTML Mouse Event Object.
**/
/**###Interface.Panel.init : method
Initialization method called automatically when panel is instantiated.
**/
/**###Interface.Panel.x : property
Number. The x position of the panel in absolute coordinates relative to the window.
**/
/**###Interface.Panel.y : property
Number. The y position of the panel in absolute coordinates relative to the window.
**/
/**###Interface.Panel.width : property
Number. The width of the panel in pixels
**/
/**###Interface.Panel.width : property
Number. The height of the panel in pixels
**/
/**###Interface.Panel.draw : method
This method tells all 'dirty' widgets stored in the shouldDraw property to draw themselves.
**/
/**###Interface.Panel.refresh : method
Clear the entire panel and then tell all widgets to draw themselves.
**/
/**###Interface.Panel.add : method
Add a new widget to the panel
param **widget** Object. The widget to be added. Motion widgets do not need to be added to the Panel
**/
/**###Interface.Panel.setBackgroundColor : method
Set the background color the panel using a css color value.
param **cssColor** String. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)'.
**/
/**###Interface.Panel.background : property
String. The default background color for all widgets in the panel. THIS IS NOT THE BACKGROUND COLOR FOR THE PANEL. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property.
**/
/**###Interface.Panel.fill : property
String. The default fill color for all widgets in the panel. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property.
**/
/**###Interface.Panel.stroke : property
String. The default stroke color for all widgets in the panel. Any valid css color, such as 'red', '#f00', or 'rgb(255,0,0)' can be assigned to this property.
**/
Interface.Panel = function() {
var self = this,
_container = arguments.length >= 1 ? arguments[0].container : undefined;
Interface.extend(this, {
type: 'Panel',
active: true,
children: [],
shouldDraw : [],
fps : 30,
useRelativeSizesAndPositions : true,
labelSize: '12px',
font:'normal 12px Helvetica',
serializeMe: ['fps', 'useRelativeSizesAndPositions', 'labelSize', 'font', 'background', 'fill', 'stroke', 'backgroundColor'],
container: (function() {
if(typeof _container === 'undefined') {
$('body').css({
margin : 0,
padding: 0,
});
var d = $('
');
d.css({
width:$(window).width(),
height:$(window).height(),
display:'block',
margin:0,
padding:0,
position:'absolute',
left:0,
top:0
});
$('body').append(d);
return d;
}else{
return _container;
}
})(),
canvas: document.createElement('canvas'),
touchEvent : function(event) {
if(self.active) {
if( typeof event.changedTouches === 'undefined' && event.originalEvent ) {
event.changedTouches = event.originalEvent.changedTouches
}
for (var j = 0; j < event.changedTouches.length; j++){
var touch = event.changedTouches.item(j);
for(var i = 0; i < self.children.length; i++) {
touch.x = touch.pageX - self.x;
touch.y = touch.pageY - self.y;
touch.type = event.type;
self.children[i].touchEvent(touch);
}
//var breakCheck = this.events[event.type].call(this, touch);
//if(breakCheck) break;
}
event.preventDefault(); // HTML Elements must simulate touch events in their touchEvent method
}
},
mouseEvent : function(e) {
if(self.active) {
if(e.type === 'mousedown') {
Interface.mouseDown = true;
}else if(e.type === 'mouseup') {
Interface.mouseDown = false;
}
var event = {
x : e.offsetX || (e.pageX - self.x), // pageX and pageY is for firefox compatibility
y : e.offsetY || (e.pageY - self.y),
type: e.type,
}
//console.log("MOUSE", event, self.y, e.pageY, e.layerY, e.clientY, e );
for(var i = 0; i < self.children.length; i++) {
self.children[i].mouseEvent(event);
}
}
},
init : function() {
var offset = $(this.container).offset();
this.width = $(this.container).width();
this.height = $(this.container).height();
this.x = offset.left;
this.y = offset.top;
if( isNaN(this.x) ) this.x = 0;
if( isNaN(this.y) ) this.y = 0;
$(this.canvas).attr({
'width': this.width,
'height': this.height,
});
$(this.container).css({ 'user-select': 'none', '-webkit-user-select': 'none'});
$(this.container).append(this.canvas);
this.ctx = this.canvas.getContext( '2d' );
this.ctx.translate(0.5, 0.5);
this.ctx.lineWidth = 1;
if(Interface.useTouch) {
$(this.container).on( 'touchstart', this.touchEvent );
$(this.container).on( 'touchmove', this.touchEvent );
$(this.container).on( 'touchend', this.touchEvent );
}else{
$(this.container).on( 'mousedown', this.mouseEvent );
$(this.container).on( 'mousemove', this.mouseEvent );
$(this.container).on( 'mouseup', this.mouseEvent );
}
$( this.container ).css({ outline: 'none' })
$( this.container ).attr( 'tabindex', 5 )
$( this.container ).on( 'keydown', this.keydown.bind( this ) )
$( this.container ).on( 'keyup', this.keyup.bind( this ) )
},
keydown: function(e) {
for( var i = 0; i < this.children.length; i++ ) {
if( this.children[i].onkeydown ) this.children[i].onkeydown(e)
}
},
keyup: function(e) {
for( var i = 0; i < this.children.length; i++ ) {
if( this.children[i].onkeyup ) this.children[i].onkeyup(e)
}
},
draw : function() {
if(this.active) {
for(var i = 0; i < this.shouldDraw.length; i++) {
this.shouldDraw[i].draw();
}
this.shouldDraw.length = 0;
}
$.publish('/draw')
},
getWidgetWithName: function( name ) {
for(var i = 0; i < this.children.length; i++) {
if( this.children[i].name === name) {
return this.children[i];
}
}
},
redoBoundaries : function() {
var offset = $(this.container).offset();
this.width = $(this.container).width();
this.height = $(this.container).height();
this.x = offset.left;
this.y = offset.top;
if( isNaN(this.x) ) this.x = 0;
if( isNaN(this.y) ) this.y = 0;
$(this.canvas).attr({
'width': this.width,
'height': this.height,
});
this.ctx.translate(0.5, 0.5);
this.ctx.lineWidth = 1;
this.refresh();
},
refresh: function() {
if(this.active) {
this.ctx.clearRect(0,0,this.canvas.width, this.canvas.height);
for(var i = 0; i < this.children.length; i++) {
this.children[i].draw();
}
}
},
add: function() {
for(var i = 0; i < arguments.length; i++) {
var widget = arguments[i];
widget.panel = this;
widget.canvas = this.canvas;
widget.container = this.container;
widget.ctx = this.ctx;
this.children.push( widget );
if(widget._init && !widget.added) widget._init();
if(widget.oninit && !widget.added) widget.oninit();
widget.draw();
widget.added = true;
if(typeof widget.add === 'function') widget.add();
}
},
clear : function() {
this.ctx.clearRect( 0,0,this.width,this.height );
this.children.length = 0;
},
remove: function(widget) {
this.ctx.clearRect( widget._x(), widget._y(), widget._width(), widget._height() );
if(typeof widget.children !== 'undefined' && widget.type !== "XY") {
for(var i = 0; i < widget.children.length; i++) {
this.children.splice( this.children.indexOf( widget.children[i] ), 1 );
Interface.widgets.splice( Interface.widgets.indexOf( widget.children[i] ), 1 );
}
}else{
if(this.children.indexOf( widget ) > -1) {
this.children.splice( this.children.indexOf( widget ), 1 );
Interface.widgets.splice( Interface.widgets.indexOf( widget ), 1 );
if(typeof widget.remove === 'function') widget.remove();
}
}
},
});
if(typeof arguments[0] !== 'undefined') Interface.extend(this, arguments[0]);
if(this.backgroundColor) this.setBackgroundColor( this.backgroundColor );
this.init();
this.timer = setInterval( function() { self.draw(); }, Math.round(1000 / this.fps) );
var childBackground ='#000',
childFill = '#666',
childStroke = '#999',
background = 'transparent',
self = this,
useRelativeSizesAndPositions = this.useRelativeSizesAndPositions;
Object.defineProperties(this, {
'useRelativeSizesAndPositions' : {
get: function() { return useRelativeSizesAndPositions; },
set: function(val) {
if(val !== useRelativeSizesAndPositions) {
useRelativeSizesAndPositions = val;
if(val === false) {
for(var i = 0; i < this.children.length; i++) {
var child = this.children[i];
child.bounds = [
Math.round( child.x * this.width ),
Math.round( child.y * this.height ),
Math.round( child.width * this.width ),
Math.round( child.height * this.height ),
];
}
}else{
for(var i = 0; i < this.children.length; i++) {
var child = this.children[i];
child.bounds = [
child.x / this.width,
child.y / this.height,
child.width / this.width,
child.height / this.height,
];
}
}
}
this.refresh();
},
},
'background': {
get: function() { return background; },
set: function(val) {
background = val;
$(this.container).css({ backgroundColor:background });
},
},
'childBackground': {
get: function() { return childBackground; },
set: function(val) {
childBackground = val;
self.refresh();
},
},
'childStroke': {
get: function() { return childStroke; },
set: function(val) {
childStroke = val;
self.refresh();
},
},
'childFill': {
get: function() { return childFill; },
set: function(val) {
childFill = val;
self.refresh();
},
}
});
if(arguments[0]) {
if(arguments[0].childBackground) this.childBackground = arguments[0].childBackground;
if(arguments[0].childFill) this.childFill = arguments[0].childFill;
if(arguments[0].childStroke) this.childStroke = arguments[0].childStroke;
}
Interface.panels.push( this );
};
var convertMouseEvent = function(eventName) {
switch(eventName) {
case 'mousedown':
return 'touchmousedown';
case 'mousemove':
return 'touchmousemove';
case 'mouseup':
return 'touchmouseup';
default:
return eventName;
}
};
var convertTouchEvent = function(eventName) {
switch(eventName) {
case 'touchstart':
return 'touchmousedown';
case 'touchmove':
return 'touchmousemove';
case 'touchend':
return 'touchmouseup';
default:
return eventName;
}
};
/**#Interface.Widget - Widget
The prototype object for all Interface.js widgets. These methods and properties are inherited by all widgets.
**/
/**###Interface.Widget.x : property
Number. The horizontal position of the widget inside of its parent panel. By default, this position is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false.
**/
/**###Interface.Widget.y : property
Number. The vertical position of the widget inside of its parent panel. By default, this position is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false.
**/
/**###Interface.Widget.width : property
Number. The width of the widget. By default, this is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false.
**/
/**###Interface.Widget.height : property
Number. The width of the widget. By default, this is determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false.
**/
/**###Interface.Widget.bounds : property
Array. A shorthand to set x,y,width and height simultaneously upon initialization. By default, these values are determined relative to the size of the widget's containing panel, but absolute values can also be used if the panel's useRelativeSizesAndPositions property is set to false.
**/
/**###Interface.Widget.min : property
Number. Default 0. The minimum value the widget should output.
**/
/**###Interface.Widget.max : property
Number. Default 1. The maximum value the widget should output.
**/
/**###Interface.Widget.ontouchstart : method
Function. A user defined event handler for whenever a touch begins over a widget.
**/
/**###Interface.Widget.ontouchmove : method
Function. A user defined event handler for whenever a touch moves over a widget.
**/
/**###Interface.Widget.ontouchend : method
Function. A user defined event handler for whenever a touch ends.
**/
/**###Interface.Widget.onmousedown : method
Function. A user defined event handler for whenever a mouse press occurs over a widget.
**/
/**###Interface.Widget.onmousemove : method
Function. A user defined event handler for whenever a mouse moves over a widget while its button is pressed.
**/
/**###Interface.Widget.onmouseup : method
Function. A user defined event handler for whenever a mouse press ends.
**/
/**###Interface.Widget.ontouchmousedown : method
Function. A user defined event handler for whenever a mouse press or touch occurs over a widget.
**/
/**###Interface.Widget.ontouchmousemove : method
Function. A user defined event handler for whenever a mouse or touch moves over a widget.
**/
/**###Interface.Widget.ontouchmouseup : method
Function. A user defined event handler for whenever a mouse press ends or a touch leaves the screen.
**/
/**###Interface.Widget.init : method
This method is called as soon as widgets are created. It copies properties passed in the constructor to the widget and also copies some default property values.
param **options** Object. A dictionary of options for the widget to be initilized with.
**/
/**###Interface.Widget.refresh : method
Tell the widget to redraw itself. This method adds the widget to the shouldDraw array of the parent panel.
**/
/**###Interface.Widget.setValue : method
Programmatically change the value of the widget. You can optionally not have the widget redraw itself when calling this method.
param **value** Number or String. The new value for the widget.
param **doNotDraw** Optional, default false. Whether or not the widget should redraw itself.
**/
/**###Interface.Widget.hitTest : method
Given an HTML touch or mouse event, determine if the event overlaps a graphical widget.
param **event** HTMLEvent. The touch or mouse event to check
**/
/**###Interface.Widget.hitTest : method
Given an HTML touch or mouse event, determine if the event overlaps a graphical widget.
param **event** HTMLEvent. The touch or mouse event to check
**/
/**###Interface.Widget.draw : method
Tell the widget to draw itself. This method must be overridden by every graphical widget.
**/
/**###Interface.Widget.mouseEvent : method
The default mouse event handler for the widget. This method also calls any user defined mouse event handlers. This method should probably never be called manually, but you might want to override it.
param **event** HTMLEvent. The mouse event to process
**/
/**###Interface.Widget.mouseEvent : method
The default touch event handler for the widget. This method also calls any user defined touch event handlers. This method should probably never be called manually, but you might want to override it.
param **event** HTMLEvent. The touch event to process
**/
/**###Interface.Widget.sendTargetMessage : method
If the widget has a target and key property, set the key property or call the key method on the target using the widgets current value.
**/
/**###Interface.Widget._background : method
returns Color. If the widget has a background color specified, return that, otherwise return the background color of the widget's parent panel.
**/
/**###Interface.Widget._stroke : method
returns Color. If the widget has a stroke color specified, return that, otherwise return the stroke color of the widget's parent panel.
**/
/**###Interface.Widget._fill : method
returns Color. If the widget has a fill color specified, return that, otherwise return the fill color of the widget's parent panel.
**/
/**###Interface.Widget._x : method
returns Number. Return the widget's x position as a pixel value relative to the position of the panel. Note that this method will always return the pixel value, even if the panel uses relative values to determine sizes and positions.
**/
/**###Interface.Widget._y : method
returns Number. Return the widget's y position as a pixel value relative to the position of the panel. Note that this method will always return the pixel value, even if the panel uses relative values to determine sizes and positions.
**/
/**###Interface.Widget._width : method
returns Number. Return the widget's width. Note that this method will always return a size in pixels, even if the panel uses relative values to determine sizes and positions.
**/
/**###Interface.Widget._height : method
returns Number. Return the widget's height. Note that this method will always return a size in pixels, even if the panel uses relative values to determine sizes and positions.
**/
var __widgetCount = 0;
var widgetDefaults = {
hasFocus : false,
requiresFocus : true,
min : 0,
max : 1,
value : 0,
lastValue : null,
name : null,
events : {
ontouchstart : null,
ontouchmove : null,
ontouchend : null,
onmousedown : null,
onmousemove : null,
onmouseup : null,
ontouchmousedown : null,
ontouchmousemove : null,
ontouchmouseup : null,
onvaluechange : null,
onboundschange : null,
},
}
Interface.Widget = {
init : function( options ) {
this.added = false;
Interface.extend( this, widgetDefaults);
if( typeof options === 'undefined' ) options = {}
this.name = options.name || this.type + "_" + __widgetCount++;
this.target = "OSC";
this.key = "/" + this.name;
Interface.extend( this, options);
if(this.bounds) {
this.x = options.bounds[0];
this.y = options.bounds[1];
this.width = options.bounds[2];
this.height = options.bounds[3];
}
if(this.colors) {
this.background = options.colors[0];
this.fill = options.colors[1];
this.stroke = options.colors[2];
}
this.focusedTouches = [];
if(this.value) this.setValue(this.value, true);
var bounds = this.bounds || [this.x, this.y, this.width, this.height],
x = this.x, y = this.y, width = this.width, height = this.height, value = this.value;
Object.defineProperties(this, {
bounds : {
configurable: true,
get : function() { return bounds; },
set : function(_bounds) {
bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3];
if( this.onboundschange ) this.onboundschange()
}
},
x : {
configurable: true,
get : function() { return x; },
set : function(val) { this.clear(); x = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); },
},
y : {
configurable: true,
get : function() { return y; },
set : function(val) { this.clear(); y = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); },
},
width : {
configurable: true,
get : function() { return width; },
set : function(val) { this.clear(); width = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); },
},
height : {
configurable: true,
get : function() { return height; },
set : function(val) { this.clear(); height = val; if( this.onboundschange ) this.onboundschange(); this.refresh(); },
},
/*value : {
configurable: true,
get : function() { return value; },
set : function(val) { if(value !== val) { value = val; this.refresh(); } },
},*/
});
Interface.widgets.push( this );
},
clear : function() {
if( this.panel ) { // must check in case widget is Acc or Gyro
this.panel.ctx.clearRect( this._x(), this._y(), this._width(), this._height() );
}
},
refresh : function() {
if(this.panel && this.panel.shouldDraw.indexOf(this) === -1) {
this.panel.shouldDraw.push(this);
}
},
setValue : function(value, doNotDraw) {
var r = this.max - this.min,
v = value;
this.value = value;
if(this.min !== 0 || this.max !== 1) {
v -= this.min;
this._value = v / r;
}else{
this._value = this.value;
}
if(!doNotDraw) this.refresh();
},
hitTest : function(e) {
if(e.x >= this._x() && e.x <= this._x() + this._width()) {
if(e.y >= this._y() && e.y <= this._y() + this._height()) {
return true;
}
}
return false;
},
mouseEvent : function(e) {
var isHit = this.hitTest(e);
var touchMouseName = convertMouseEvent(e.type);
if(isHit || this.hasFocus || !this.requiresFocus) {
if(e.type === 'mousedown') this.hasFocus = true;
if(this[e.type]) this[e.type](e, isHit); // normal event
if(this['on'+e.type]) this['on'+e.type](e, isHit); // user defined event
if(this['on'+touchMouseName]) this['on'+touchMouseName](e, isHit); // user defined event
}
if(e.type === 'mouseup') this.hasFocus = false;
},
touchEvent : function(touch) { // event type is stored in touch by Panel
var isHit = this.hitTest(touch);
var touchMouseName = convertTouchEvent(touch.type);
if(isHit || this.hasFocus || !this.requiresFocus) {
if(touch.type === 'touchstart') {
this.focusedTouches.push(touch);
this.hasFocus = true;
}
if(this[touch.type])
this[touch.type](touch, isHit); // normal event
if(this['on'+touch.type]) this['on'+touch.type](touch, isHit); // user defined event
if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event
}
if(touch.type === 'touchend') {
for(var i = 0; i < this.focusedTouches.length; i++) {
if(this.focusedTouches[i].id === touch.id) {
this.focusedTouches.splice(i, 1);
if(this.focusedTouches.length === 0) this.hasFocus = false;
break;
}
}
}
},
draw : function() {},
sendTargetMessage : function() {
if(this.target && this.key) {
if(this.target === "OSC") {
if(Interface.OSC) {
if(typeof this.values === 'undefined') {
var tt = typeof this.value === 'string' ? 's' : 'f';
Interface.OSC.send(this.key, tt, [ this.value ] );
}else{
if(typeof this.sendValues === 'undefined') {
var tt = '';
for(var i = 0; i < this.values.length; i++) {
tt += typeof this.value === 'string' ? 's' : 'f';
}
Interface.OSC.send( this.key, tt, this.values );
}else{
this.sendValues();
}
}
}
}else if(this.target === "MIDI") {
if(Interface.MIDI && typeof this.values === 'undefined') {
Interface.MIDI.send( this.key[0],this.key[1],this.key[2], this.value )
}
}else{
if(typeof this.target[this.key] === 'function') {
this.target[this.key]( this.values || this.value );
}else{
this.target[this.key] = this.values || this.value;
}
}
}
},
_background : function() { return this.background || this.panel.childBackground; },
_stroke : function() { return this.stroke || this.panel.childStroke; },
_fill : function() { return this.fill || this.panel.childFill; },
_x : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.x * this.panel.width) : this.x; },
_y : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.y * this.panel.height) : this.y; },
_width : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.width * this.panel.width) : this.width; },
_height : function() { return this.panel.useRelativeSizesAndPositions ? Math.floor(this.height * this.panel.height) : this.height; },
_font : function() {
var font = this.font || this.panel.font;
return font;
},
label:null,
_serializeMe : [
"background", "stroke", "fill", "x", "y", "width", "height", "value",
"label", "onmousedown", "onmousemove", "onmouseup", "ontouchmousedown", "ontouchmousemove", "ontouchmouseup",
"ontouchstart", "ontouchmove", "ontouchend", "onvaluechange", "name", "type", "target", "key"
],
};
Interface.HBox = function() {
var me = this
Interface.extend(this, {
type : 'HBox',
children: [],
proxyPanel : { active:true, x:0, y:0, width:1, height:1, shouldDraw:[], useRelativeSizesAndPositions:true }, // needed for absolute widths / heights which are set in _init call
add: function() {
for( var i = 0; i < arguments.length; i++ ) {
var child = arguments[ i ]
if( this.children.indexOf( child ) === -1 ) this.children.push( child )
child.panel = this.proxyPanel
child.ctx = this.panel.ctx
}
this.layout()
this.draw()
},
layout : function() {
var w = (this.width / this.children.length) / this.width,
_widthUsed = 0;
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
child.x = _widthUsed + this.x
child.y = this.y * (1 / this.height)
child.width = w
child.height = 1
_widthUsed += w
}
return this
},
draw: function() {
this.proxyPanel.width = this._width()
this.proxyPanel.height = this._height()
for( var i = 0; i < this.children.length; i++ ) {
this.children[ i ].draw()
}
},
refresh: function() {
for( var i = 0; i < this.proxyPanel.shouldDraw.length; i++ ) {
this.proxyPanel.shouldDraw[ i ].draw()
}
this.proxyPanel.shouldDraw.length = 0
},
mouseEvent: function(e){
// e.x -= this._x()
// e.y -= this._y()
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
this.children[ i ].mouseEvent( e )
}
},
touchEvent: function(e){
// e.x -= this._x()
// e.y -= this._y()
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
this.children[ i ].touchEvent( e )
}
},
_init: function() {
this.useRelativeSizesAndPositions = this.panel.useRelativeSizesAndPositions
this.proxyPanel.width = this._width()
this.proxyPanel.height = this._height()
this.proxyPanel.__proto__ = this.panel
$.subscribe('/draw', this.refresh.bind( this ) )
Object.defineProperties(this, {
bounds : {
configurable: true,
get : function() { return bounds; },
set : function(_bounds) {
bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3];
this.layout()
this.draw()
}
},
})
}
})
.init( arguments[0] )
};
Interface.HBox.prototype = Interface.Widget;
Interface.VBox = function() {
Interface.extend(this, {
type : 'VBox',
children: [],
proxyPanel : { active:true, x:0, y:0, width:1, height:1, shouldDraw:[], useRelativeSizesAndPositions:true }, // needed for absolute widths / heights which are set in _init call
add: function() {
for( var i = 0; i < arguments.length; i++ ) {
var child = arguments[ i ]
this.children.push( child )
child.panel = this.proxyPanel
child.ctx = this.panel.ctx
}
this.layout()
this.draw()
},
layout : function() {
var h = this.height / this.children.length,
_heightUsed = 0;
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
child.x = this.x
child.y = (this.y + _heightUsed ) * (1 / this.height)
//
child.width = 1
child.height = h * ( 1/this.height)
_heightUsed += h
}
return this
},
draw: function() {
this.proxyPanel.width = this._width()
this.proxyPanel.height = this._height()
for( var i = 0; i < this.children.length; i++ ) {
this.children[ i ].draw()
}
},
refresh: function() {
for( var i = 0; i < this.proxyPanel.shouldDraw.length; i++ ) {
this.proxyPanel.shouldDraw[ i ].draw()
}
this.proxyPanel.shouldDraw.length = 0
},
mouseEvent: function(e){
// e.x -= this._x()
// e.y -= this._y()
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
this.children[ i ].mouseEvent( e )
}
},
touchEvent: function(e){
e.x -= this._x()
e.y -= this._y()
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ]
this.children[ i ].touchEvent( e )
}
},
_init: function() {
this.useRelativeSizesAndPositions = this.panel.useRelativeSizesAndPositions
this.proxyPanel.width = this._width()
this.proxyPanel.height = this._height()
this.proxyPanel.__proto__ = this.panel
$.subscribe('/draw', this.refresh.bind( this ) )
Object.defineProperties(this, {
bounds : {
configurable: true,
get : function() { return bounds; },
set : function(_bounds) {
bounds = _bounds; this.x = bounds[0]; this.y = bounds[1]; this.width = bounds[2]; this.height = bounds[3];
this.layout()
this.draw()
}
},
})
}
})
.init( arguments[0] )
};
Interface.VBox.prototype = Interface.Widget;
/**#Interface.Slider - Widget
A vertical or horizontal slider.
## Example Usage##
`a = new Interface.Slider({ bounds:[0,0,1,.2], isVertical:false });
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the slider on initialization.
- - - -
**/
/**###Interface.Slider.isVertical : property
Boolean. Whether or not the slider draws itself vertically or horizontally. Note this does not affect the boundaries of the slider, just the orientation of the slider's movement.
**/
Interface.Slider = function() {
Interface.extend(this, {
type : 'Slider',
isVertical : true,
serializeMe : ["isVertical"],
draw : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height= this._height();
this.ctx.fillStyle = this._background();
this.ctx.fillRect( x, y, width, height );
this.ctx.fillStyle = this._fill();
if(this.isVertical) {
this.ctx.fillRect( x, y + height - this._value * height, width, this._value * height);
}else{
this.ctx.fillRect( x, y, width * this._value, height);
}
if(this.label) {
this.ctx.fillStyle = this._stroke();
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
this.ctx.font = this._font();
this.ctx.fillText(this.label, x + width / 2, y + height / 2);
}
this.ctx.strokeStyle = this._stroke();
this.ctx.strokeRect( x, y, width, height );
},
changeValue : function( xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
this._value = this.isVertical ? 1 - (yOffset / this._height()) : xOffset / this._width();
if(this._value < 0) {
this._value = 0;
// this.hasFocus = false;
}else if(this._value > 1) {
this._value = 1;
// this.hasFocus = false;
}
this.value = this.min + (this.max - this.min) * this._value;
if(this.value !== this.lastValue) {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.refresh();
this.lastValue = this.value;
}
}
},
mousedown : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); },
mousemove : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); },
mouseup : function(e, hit) { if(hit && Interface.mouseDown) this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
})
.init( arguments[0] );
};
Interface.Slider.prototype = Interface.Widget;
/**#Interface.Crossfader - Widget
A horizontal crossfader.
## Example Usage##
`a = new Interface.Crossfader({ bounds:[0,0,1,.2], crossfaderWidth:20 });
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the crossfader on initialization.
- - - -
**/
/**###Interface.Crossfader.crossfaderWidth : property
Boolean. The width of the rectangle indicating the current position of the crossfader, in pixel values. TODO: use relative values when used by the panel.
**/
Interface.Crossfader = function() {
Interface.extend(this, {
type : 'Crossfader',
crossfaderWidth: 30,
serializeMe : ["crossfaderWidth"],
_value : .5,
draw : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height= this._height();
this.ctx.fillStyle = this._background();
this.ctx.fillRect( x, y, width, height );
this.ctx.fillStyle = this._fill();
this.ctx.fillRect( x + (width - this.crossfaderWidth) * this._value, y, this.crossfaderWidth, height);
this.ctx.strokeStyle = this._stroke();
this.ctx.strokeRect( x, y, width, height );
},
changeValue : function( xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
this._value = xOffset / this._width();
if(this._value < 0) {
this._value = 0;
//this.hasFocus = false;
}else if(this._value > 1) {
this._value = 1;
//this.hasFocus = false;
}
this.value = this.min + (this.max - this.min) * this._value;
if(this.value !== this.lastValue) {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.refresh();
this.lastValue = this.value;
}
}
},
mousedown : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); },
mousemove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); },
mouseup : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchstart : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchmove : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchend : function(e, hit) { if(hit) this.changeValue( e.x - this._x(), e.y - this._y() ); },
})
.init( arguments[0] );
};
Interface.Crossfader.prototype = Interface.Widget;
/**#Interface.Button - Widget
A button with a variety of on/off modes
## Example Usage##
`a = new Interface.Button({ bounds:[0,0,.25,.25], mode:'contact', label:'test' });
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the button on initialization.
- - - -
**/
/**###Interface.Button.mode : property
String. Can be 'toggle', 'momentary' or 'contact'. In toggle mode, the button turns on when it is pressed and off when it is pressed again. In momentary mode, the button turns on when pressed and off when released. In contact mode, the button briefly flashes when pressed and sends its value.
**/
/**###Interface.Button.label : property
String. A text label to print in the center of the button.
**/
Interface.Button = function() {
Interface.extend(this, {
type : 'Button',
_value: 0,
serializeMe : ["mode", "label"],
mode : 'toggle',
isMouseOver : false,
isTouchOver : false,
label : null,
requiresFocus : false,
draw : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height= this._height();
if(this._value) {
this.ctx.fillStyle = this._fill();
}else{
this.ctx.fillStyle = this._background();
}
this.ctx.fillRect( x, y, width, height );
if(this.label !== null) {
this.ctx.fillStyle = this._stroke();
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
this.ctx.font = this._font();
this.ctx.fillText(this.label, x + width / 2, y + height / 2);
}
this.ctx.strokeStyle = this._stroke();
this.ctx.strokeRect( x, y, width, height );
},
changeValue : function( xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
this._value = !this._value;
this.value = this._value ? this.max : this.min;
if(this.value !== this.lastValue || this.mode === 'contact') {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.draw();
this.lastValue = this.value;
}
}
},
setValue : function(value, doNotDraw) {
var r = this.max - this.min,
v = value;
this.value = value;
if(this.min !== 0 || this.max !== 1) {
v -= this.min;
this._value = v / r;
}else{
this._value = this.value;
}
this.lastValue = this.value;
if(!doNotDraw && this.mode !== 'contact') this.refresh();
},
mousedown : function(e, hit) {
if(hit && Interface.mouseDown) {
this.isMouseOver = true;
this.changeValue();
if(this.mode === 'contact') {
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}
},
mousemove : function(e, hit) {
if(!this.requiresFocus && hit && Interface.mouseDown && !this.isMouseOver) {
this.isMouseOver = true;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}else if(!hit && this.isMouseOver) {
console.log( 'moved off!' )
this.isMouseOver = false;
}
},
mouseup : function(e) {
if(this.mode === 'momentary') {
this.changeValue();// e.x - this.x, e.y - this.y );
this.isMouseOver = false;
}
},
touchstart : function(e, hit) {
if(hit) {
this.isTouchOver = true;
this.changeValue();
if(this.mode === 'contact') {
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}
},
touchmove : function(e, hit) {
if(!this.requiresFocus && hit && !this.isTouchOver) {
this.isTouchOver = true;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}else if(!hit && this.isTouchOver) {
this.isTouchOver = false;
}
},
touchend : function(e) {
this.isTouchOver = false;
if(this.mode === 'momentary')
this.changeValue();// e.x - this.x, e.y - this.y );
},
})
.init( arguments[0] );
};
Interface.Button.prototype = Interface.Widget;
/**#Interface.ButtonV - Widget
A button with a customizable shape and variety of on/off modes
*contributed by Jonathan Simozar
## Example Usage##
`a = new Interface.ButtonV({
bounds:[.25,0,.125,.8],
points: [{x:1,y:0},{x:.5,y:0},{x:.5,y:.5},{x:0,y:.5},{x:0,y:1},{x:1,y:1},{x:1,y:0}],
mode:'contact',
label:'test',
textLocation : {x:.5, y:.75},
});
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the button on initialization.
- - - -
**/
/**###Interface.ButtonV.points : property
Array. A set of coordinates used to customize the button shape. The coordinates are connected in the order of the indices. The first and last point must be the same.
**/
/**###Interface.ButtonV.mode : property
String. Can be 'toggle', 'momentary' or 'contact'. In toggle mode, the button turns on when it is pressed and off when it is pressed again. In momentary mode, the button turns on when pressed and off when released. In contact mode, the button briefly flashes when pressed and sends its value.
**/
/**###Interface.ButtonV.label : property
String. A text label to print at the textLocation coordinates of the button.
**/
/**###Interface.ButtonV.textLocation : property
Set. A set of x and y coordinates which position the the label within the bounds.
**/
Interface.ButtonV = function() {
Interface.extend(this, {
type : 'ButtonV',
_value: 0,
serializeMe : ["mode", "label"],
mode : 'toggle',
isMouseOver : false,
isTouchOver : false,
label : null,
points : [{x : 0, y : 0}, {x : 0, y : 1}, {x : 1,y : 1}, {x : 1, y : 0}, {x : 0, y : 0}],
textLocation : {x:.5, y:.5},
draw : function() {
var x = this._x(),
y = this._y(),
i = 0,
width = this._width(),
height= this._height();
if(this._value) {
this.ctx.fillStyle = this._fill();
}else{
this.ctx.fillStyle = this._background();
}
this.ctx.beginPath();
this.ctx.strokeStyle = this._stroke();
for (i; i < this.points.length; i++) {
if (i === 0) {
this.ctx.moveTo(x + this.points[i].x*width, y + this.points[i].y*height);
}
else
this.ctx.lineTo(x + this.points[i].x*width, y + this.points[i].y*height);
} //this.points[i].x is how to reference points.x
this.ctx.lineTo(x + this.points[0].x*width, y + this.points[0].y*height);
this.ctx.closePath();
this.ctx.fill();
this.ctx.stroke();
if(this.label !== null) {
this.ctx.fillStyle = this._stroke();
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
this.ctx.font = this._font();
this.ctx.fillText(this.label, x + width*this.textLocation.x, y + height*this.textLocation.y);
}
},
changeValue : function( xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
this._value = !this._value;
this.value = this._value ? this.max : this.min;
if(this.value !== this.lastValue || this.mode === 'contact') {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.draw();
this.lastValue = this.value;
}
}
},
hitTest : function(e) {
var w = this._width(),
h = this._height(),
x = this._x(),
y = this._y();
if(e.x >= x && e.x <= x + w) {
if(e.y >= y && e.y <= y + h) {
var i = 0,
p = this.points,
sides = 0;
for (i; i < p.length - 1; i++) {
if(p[i+1].x > p[i].x) {
if((p[i].x * w + x) <= e.x && e.x < (p[i+1].x * w + x)) {
var yval = (p[i+1].y - p[i].y)/(p[i+1].x - p[i].x) * h/w * (e.x - p[i].x * w + x) + p[i].y * h + y;
if(yval - e.y < 0)
sides++;
}
}
else if (p[i+1].x < p[i].x) {
if(p[i].x * w + x >= e.x && e.x > p[i+1].x * w + x) {
var yval = (p[i+1].y - p[i].y)/(p[i+1].x - p[i].x) * h/w * (e.x - p[i].x * w + x) + p[i].y * h + y;
if(yval - e.y < 0)
sides++;
}
}
}
if (sides % 2 == 1)
return true;
}
}
return false;
},
setValue : function(value, doNotDraw) {
var r = this.max - this.min,
v = value;
this.value = value;
if(this.min !== 0 || this.max !== 1) {
v -= this.min;
this._value = v / r;
}else{
this._value = this.value;
}
this.lastValue = this.value;
if(!doNotDraw && this.mode !== 'contact') this.refresh();
},
mousedown : function(e, hit) {
if(hit && Interface.mouseDown) {
this.isMouseOver = true;
this.changeValue();
if(this.mode === 'contact') {
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}
},
mousemove : function(e, hit) {
if(!this.requiresFocus && hit && Interface.mouseDown && !this.isMouseOver) {
this.isMouseOver = true;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}else if(!hit && this.isMouseOver) {
this.isMouseOver = false;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}
},
mouseup : function(e) {
if(this.mode === 'momentary') {
if( this.requiresFocus || ( !this.requiresFocus && this.isMouseOver) ) {
this.isMouseOver = false;
this.changeValue();
}
}
},
touchstart : function(e, hit) {
if(hit) {
this.isTouchOver = true;
this.changeValue();
if(this.mode === 'contact') {
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}
},
touchmove : function(e, hit) {
if(!this.requiresFocus && hit && !this.isTouchOver) {
this.isTouchOver = true;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}else if(!hit && this.isTouchOver) {
this.isTouchOver = false;
if(this.mode !== 'contact') {
this.changeValue();// e.x - this.x, e.y - this.y );
}else{
this._value = 1;
this.draw();
var self = this;
setTimeout( function() { self._value = 0; self.draw(); }, 75);
}
}else if(!hit && this.isTouchOver) {
this.isTouchOver = false;
}
},
touchend : function(e) {
if( this.momentary && this.requiresFocus || ( !this.requiresFocus && this.isTouchOver) ) {
this.changeValue();
}
this.isTouchOver = false;
},
})
.init( arguments[0] );
};
Interface.ButtonV.prototype = Interface.Widget;
/**#Interface.Piano - Widget
A piano with adjustable ranges of pitches
*contributed by Jonathan Simozar, with modifications by thecharlie
## Example Usage##
`var c = new Interface.Piano({
bounds:[0,0,.8,.5],
startletter : "C",
startoctave : 3,
endletter : "C",
endoctave : 5,
noteLabels : true,
target: synth,
onvaluechange : function() {this.target.note (this.frequency, this.value)},
});
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the piano on initialization.
- - - -
**/
/**###Interface.Piano.onvaluechange : method
The event handler fired whenever a piano update is received. Used to fire the the event handler for when a button update is recieved.
**/
/**###Interface.Piano.endoctave : property
Number. A number corresponding to the ending octave of the last note in the desired range.
**/
/**###Interface.Piano.startletter : property
String. A letter corresponding to the starting pitch for the desired range. To start on an accidental use sharps, not flats. For example, C#.
**/
/**###Interface.Piano.startoctave : property
Number. A number corresponding to the starting octave of the first note in the desired range.
**/
/**###Interface.Piano.endletter : property
String. A letter corresponding to the ending pitch for the desired range. To end on an accidental use sharps, not flats. For example, C#.
**/
/**###Interface.Piano.endoctave : property
Number. A number corresponding to the ending octave of the last note in the desired range.
**/
/**###Interface.Piano.noteLabels : property
Boolean. A boolean corresponding to showing the note labels when true and hiding the note labels when false.
**/
/**###Interface.Piano.target : property
Object. The instrument used to make sound on each key.
**/
Interface.Piano = function() {
Interface.extend(this, {
type : 'Piano',
_value: 0,
serializeMe : ['mode', 'label'],
mode : 'toggle',
isMouseOver : false,
isTouchOver : false,
label : null,
startletter : 'C',
startoctave : 3,
endletter : 'C',
endoctave : 5,
target : null,
noteLabels : false,
_initialized : false,
keyMap: [ 'Z','S','X','D','C','V','G','B','H','N','J','M',','],
children: [],
play: function( noteNum, duration ) {
if( isNaN(duration) ) {
duration = 4410
}
if( typeof Gibber !== 'undefined' ) { duration = Gibber.Clock.time( duration ) }
var child = this.children[ noteNum ]
if( child ) {
child.changeValue()
future( function() { if( child._value == 1 ) child.changeValue() }, duration )
}
},
onkeyup: function( e ) {
var c = Interface.keyCodeToChar[ e.keyCode ],
keyNum = this.keyMap.indexOf( c ),
child = this.children[ keyNum ]
if( typeof child !== 'undefined' && child._value == 1 ) {
child.changeValue()
}
},
onkeydown: function( e ) {
var c = Interface.keyCodeToChar[ e.keyCode ],
keyNum = this.keyMap.indexOf( c ),
child = this.children[ keyNum ]
if( typeof child !== 'undefined' && child._value == 0 ) {
child.changeValue()
}
},
onvaluechange : function() { this.values = [this.frequency,this.value] },
onboundschange: function() { if( this._initialized) this.placeKeys() },
draw : function() {
for( var i = 0; i < this.children.length; i++ ) { this.children[i].refresh() }
return this
},
placeKeys: function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height = this._height(),
octave = this.startoctave,
startnote = 0,
endnote = 0,
keylabel = ["0","C","C#/Db","D","D#/Eb","E","F","F#/Gb","G","G#/Ab","A","A#/Bb","B"],
keyid = ["0","C","C#","D","D#","E","F","F#","G","G#","A","A#","B"],
keynid = [0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7],
notes = ( endnote + this.endoctave * 12) - (startnote + this.startoctave * 12) + 1,
dist = ( keynid[ endnote ] + this.endoctave * 7 ) - ( keynid[ startnote ] + this.startoctave * 7 ) + 1,
j = 0;
if( this._initialized ) {
this.clear()
for( var i = this.children.length - 1; i >= 0; i-- ) {
var key = this.children.pop()
this.panel.remove( key )
}
}
for (var i = 1; i < 13; i++) {
if ( this.startletter === keyid[ i ] ) startnote = i;
if ( this.endletter === keyid[ i ] ) endnote = i;
}
if ( [ 2,4,7,9,11 ].indexOf( endnote ) > -1 ) dist--;
for (var i = 0; i < notes - 1; i++ ) {
var points, textLocation, bg, fg, bounds, label
switch( startnote ) {
case 1:
points = [{x:0,y:0},{x:.6,y:0},{x:.6,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:0}] // left
bg = this._fill()
textLocation = { x:.5, y:.75 }
fg = this._background()
bounds = [ j/dist*this.width + this.x, this.y, this.width/dist, this.height ]
label = this.noteLabels ? keylabel[startnote] + octave : null
break;
case 2:
points = [{x:.1,y:0},{x:.7,y:0},{x:.7,y:1},{x:.1,y:1},{x:.1,y:0}] //black
textLocation = { x:.3925, y:.5 }
bg = this._background()
fg = this._fill()
bounds = [(j-.5)/dist *this.width + this.x, this.y,this.width/dist,.625*this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 3:
points = [{x:.2,y:0},{x:.8,y:0},{x:.8,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:.2,y:.625},{x:.2,y:0}] // mid
textLocation = { x:.5, y:.75 }
bg = this._fill()
fg = this._background()
bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 4:
points = [{x:.3,y:0},{x:.9,y:0},{x:.9,y:1},{x:.3,y:1},{x:.3,y:0}], //black
textLocation = {x:.6075, y:.5}
bg = this._background()
fg = this._fill()
bounds = [(j-.5)/dist *this.width + this.x, this.y,this.width/dist,.625*this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 5:
points = [{x:1,y:0},{x:.4,y:0},{x:.4,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}] //right
textLocation = {x:.5, y:.75}
bg = this._fill()
fg = this._background()
bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 6:
points = [{x:0,y:0},{x:0.57142857,y:0},{x:0.57142857,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:0}] //left
textLocation = {x:.5, y:.75}
bg = this._fill()
fg = this._background()
bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 7:
points = [{x:0.07142857,y:0},{x:0.64285714,y:0},{x:0.64285714,y:1},{x:0.07142857,y:1},{x:0.07142857,y:0}] //black
textLocation = {x:.3925, y:.5}
bg = this._background()
fg = this._fill()
bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 8:
points = [{x:0.14285714,y:0},{x:0.71428571,y:0},{x:0.71428571,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:0.14285714,y:.625},{x:0.14285714,y:0}], //middle
textLocation = {x:.5, y:.75}
bg = this._fill()
fg = this._background()
bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 9:
points = [{x:0.21428571,y:0},{x:0.78571428,y:0},{x:0.78571428,y:1},{x:0.21428571,y:1},{x:0.21428571,y:0}] //black
bg = this._background()
fg = this._fill()
bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 10:
points = [{x:0.28571428,y:0},{x:0.85714285,y:0},{x:0.85714285,y:.625},{x:1,y:.625},{x:1,y:1},{x:0,y:1},{x:0,y:.625},{x:0.28571428,y:.625},{x:0.28571428,y:0}], //middle
bg = this._fill()
fg = this._background()
textLocation = {x:.5, y:.75}
bounds = [j/dist*this.width + this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 11:
points = [{x:0.35714285,y:0},{x:0.92857142,y:0},{x:0.92857142,y:1},{x:0.35714285,y:1},{x:0.35714285,y:0}], //black
bg = this._background()
fg = this._fill()
textLocation = {x:.6075, y:.5}
bounds = [(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
case 12:
points = [{x:1,y:0},{x:0.42857142,y:0},{x:0.42857142,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}] //right
bg = this._fill()
fg = this._background()
textLocation = {x:.5, y:.75}
bounds = [j/dist*this.width+ this.x,this.y,this.width/dist,this.height]
label = this.noteLabels ? keylabel[startnote] : null
break;
}
var _key = new Interface.ButtonV({
points: points,
textLocation : textLocation,
target : this.target,
onvaluechange: this.onvaluechange,
frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626,
background: bg,
fill: fg,
stroke: this._stroke(),
bounds: bounds,
label: label,
requiresFocus : false,
mode:'momentary',
});
if ( [ 2,4,7,9,11 ].indexOf( startnote ) === -1 ) j++;
this.children.push(_key)
this.panel.add(_key);
startnote++;
if (startnote > 12) {
startnote = 1;
octave++;
}
}
if (startnote == 2 || startnote == 4 || startnote == 7 || startnote == 9 || startnote == 11)
var pkeys = new Interface.ButtonV({
points: [{x:.166,y:0},{x:.5,y:0},{x:.5,y:1},{x:.166,y:1},{x:.166,y:0}], //black
target : this.target,
onvaluechange: this.onvaluechange,
background: this._background(),
frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626,
bounds:[(j-.5)/dist*this.width + this.x, this.y,this.width/dist,.625*this.height],
label: this.noteLabels ? keylabel[startnote] : null,
stroke: this._stroke(),
requiresFocus : false,
mode:'momentary'
});
else if (startnote == 1)
var pkeys = new Interface.ButtonV({
textLocation : {x:.5, y:.75},
target : this.target,
onvaluechange: this.onvaluechange,
frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626,
background: this._fill(),
fill: this._background(),
stroke: this._stroke(),
bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height],
label: this.noteLabels ? keylabel[startnote] + octave : null,
requiresFocus : false,
mode:'momentary'
});
else if (startnote == 4)
var pkeys = new Interface.ButtonV({
textLocation : {x:.5, y:.75},
target : this.target,
onvaluechange: this.onvaluechange,
frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626,
background: this._fill(),
fill: this._background(),
stroke: this._stroke(),
bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height],
label: this.noteLabels ? keylabel[startnote] : null,
requiresFocus : false,
mode:'momentary'
});
else
var pkeys = new Interface.ButtonV({
points: [{x:1,y:0},{x:.33,y:0},{x:.33,y:.625},{x:0,y:.625},{x:0,y:1},{x:1,y:1},{x:1,y:0}], //right
textLocation : {x:.5, y:.75},
target : this.target,
onvaluechange: this.onvaluechange,
frequency: Math.pow(2,(startnote + 12*octave - 49)/12)*261.626,
background: this._fill(),
fill: this._background(),
stroke: this._stroke(),
bounds:[j/dist*this.width + this.x,this.y,this.width/dist,this.height],
label: this.noteLabels ? keylabel[startnote] : null,
requiresFocus : false,
mode:'momentary'
});
this.children.push(pkeys)
this.panel.add(pkeys);
this._initialized = true
},
_init : function() {
this.placeKeys()
}
})
.init( arguments[0] );
};
Interface.Piano.prototype = Interface.Widget;
/**#Interface.Knob - Widget
A virtual knob.
## Example Usage##
`a = new Interface.Knob({ x:.1, y:.1, radius:.3 });
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the knob on initialization.
- - - -
**/
/**###Interface.Knob.radius : property
Number. The size of the Knob.
**/
/**###Interface.Knob.knobBuffer : property
Number. The size of the space in the middle of the knob.
**/
/**###Interface.Knob.centerZero : property
Number. If true, the knob is centered at zero. Useful for panning knobs.
**/
/**###Interface.Knob.usesRotation : property
Number. If true, the knob value is determined by the slope of the touch or mouse event in relation to the knob. When false, the user simply presses the knob and moves their finger/mouse up and down to change its value.
**/
Interface.Knob = function() {
Interface.extend(this, {
type : 'Knob',
_value: 0,
serializeMe : ["usesRotation", "knobBuffer"],
knobBuffer:3,
lastPosition: 0,
usesRotation: true,
draw : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height= this._height(),
radius = width / 2;
this.ctx.clearRect(x, y, radius * 2,radius * 2);
this.ctx.strokeStyle = this._stroke();
this.ctx.fillStyle = this._background(); // draw background of widget first
var angle0 = Math.PI * .6;
var angle1 = Math.PI * .4;
this.ctx.beginPath();
this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle1, false);
this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3 , angle1, angle0, true);
this.ctx.closePath();
this.ctx.fill();
this.ctx.fillStyle = this._fill(); // now draw foreground...
if(this.centerZero) {
var angle3 = Math.PI * 1.5;
var angle4;
if(this._value >= .5) {
angle4 = Math.PI * (1.5 + (this._value - .5) * 1.8); // from 1.5 to 2.4
}else{
angle4 = Math.PI * (1.5 - ((1 - this._value * 2) * .9)); // from 1.5 to .6
}
if(this._value > Math.PI * 1.8) this._value -= Math.PI * 1.8; // wrap around
this.ctx.beginPath();
this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle3, angle4, (this._value < .5));
this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * 0.3, angle4, angle3, (this._value > .5));
this.ctx.closePath();
// if(this._value > .495 && this._value < .505) { // draw circle if centered?
// this.ctx.beginPath();
// this.ctx.arc(this.x + radius , this.y + radius, (radius - this.knobBuffer) * .3, 0, Math.PI*2, true);
// this.ctx.closePath();
// }
this.ctx.fill();
} else {
if(!this.isInverted) {
var angle2 = Math.PI * .6 + this._value * 1.8 * Math.PI;
if(angle2 > 2 * Math.PI) angle2 -= 2 * Math.PI;
}else{
var angle2 = Math.PI * (0.4 - (1.8 * this._value));
}
this.ctx.beginPath();
if(!this.isInverted) {
this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle2, false);
this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3, angle2, angle0, true);
} else {
this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle1, angle2 ,true);
this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3, angle2, angle1, false);
}
this.ctx.closePath();
this.ctx.fill();
}
this.ctx.beginPath();
this.ctx.arc(x + radius, y + radius, radius - this.knobBuffer, angle0, angle1, false);
this.ctx.arc(x + radius, y + radius, (radius - this.knobBuffer) * .3 , angle1, angle0, true);
this.ctx.closePath();
this.ctx.stroke();
if(this.label !== null) {
this.ctx.fillStyle = this._stroke();
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
this.ctx.font = this._font();
this.ctx.fillText(this.label, x + radius, y + radius * 2.25);
}
},
setValue : function(value, doNotDraw) {
var r = this.max - this.min,
v = value;
this.lastValue = this.value;
this.value = value;
if(this.min !== 0 || this.max !== 1) {
v -= this.min;
this._value = v / r;
}else{
this._value = this.value;
}
if(this.value !== this.lastValue) {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.refresh();
this.lastValue = this.value;
}
if(!doNotDraw) this.refresh();
},
changeValue : function( xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
var radius = this._width() / 2;
this.lastValue = this.value;
if(!this.usesRotation) {
if (this.lastPosition != -1) {
this._value -= (yOffset - this.lastPosition) / (radius * 2);
}
}else{
var xdiff = radius - xOffset;
var ydiff = radius - yOffset;
var angle = Math.PI + Math.atan2(ydiff, xdiff);
this._value = ((angle + (Math.PI * 1.5)) % (Math.PI * 2)) / (Math.PI * 2);
if(this.lastRotationValue > .8 && this._value < .2) {
this._value = 1;
}else if(this.lastRotationValue < .2 && this._value > .8) {
this._value = 0;
}
}
if (this._value > 1) this._value = 1;
if (this._value < 0) this._value = 0;
this.lastRotationValue = this._value;
this.lastPosition = yOffset;
var range = this.max - this.min;
this.value = this.min + this._value * range;
if(this.value !== this.lastValue) {
this.sendTargetMessage();
if(this.onvaluechange) this.onvaluechange();
this.refresh();
this.lastValue = this.value;
}
}
},
hitTest : function(e) {
if( e.x >= this._x() && e.x < this._x() + this._width() ) {
if( e.y >= this._y() && e.y < this._y() + this._width() ) {
return true;
}
}
return false;
},
mousedown : function(e) {
this.lastPosition = e.y - this._y();
this.changeValue( e.x - this._x(), e.y - this._y() );
},
mousemove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); },
mouseup : function(e) {},
touchstart : function(e) {
this.lastPosition = e.y - this._y();
this.changeValue( e.x - this._x(), e.y - this._y() );
},
touchmove : function(e) { this.changeValue( e.x - this._x(), e.y - this._y() ); },
touchend : function(e) {},
_init : function() {
var width = this.width,
height = this.height;
Object.defineProperty(this, 'width', {
configurable: true,
get : function() { return width; },
set : function(_width) { this.clear(); width = height = _width; this.refresh(); }
});
Object.defineProperty(this, 'height', {
configurable: true,
get : function() { return height; },
set : function(_height) { height = _height; }
});
},
})
.init( arguments[0] );
};
Interface.Knob.prototype = Interface.Widget;
function sign(n) {
if(n < 0) return -1;
return 1;
}
/**#Interface.XY - Widget
A multitouch XY controller with optional built-in physics.
## Example Usage##
`a = new Interface.XY({ x:0, y:0, numChildren:2 });
panel = new Interface.Panel();
panel.add(a);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the XY on initialization.
- - - -
**/
/**###Interface.XY.childWidth : property
Number. The size of the children, currently in pixels. TODO: use relative values when the panel is using relative sizes and positions.
**/
/**###Interface.XY.usePhysics : property
Boolean. Wheter or not the physics engine should be turned on.
**/
/**###Interface.XY.friction : property
Number. Default .9. The amount of friction in the physics system. High values mean children will decelerate quicker.
**/
/**###Interface.XY.maxVelocity : property
Number. Default 10. The maximum velocity for each child.
**/
/**###Interface.XY.detectCollisions : property
Boolean. Default true. When true, children bounce off one another.
**/
/**###Interface.XY.values : property
Array. An array of objects taking the form {x,y} that store the x and y positions of every child. So, to get the x position of child #0: myXY.values[0].x
**/
/**###Interface.XY.children : property
Array. An array of objects representing the various children of the widget.
**/
/**###Interface.XY.animate : method
This is called to run the physics engine, draw widgets with updated positions, change values of widgets and call appropriate event handlers.
**/
Interface.XY = function() {
var self = this,
posDiff = {x:0, y:0},
velDiff = {x:0, y:0},
normal = {x:0, y:0},
cDot = 0;
Interface.extend(this, {
type : 'XY',
_value : 0,
serializeMe : ["childWidth", "childHeight", "numChildren", "usePhysics", "values", "friction", "maxVelocity", "detectCollisions", "fps"],
childWidth : 25,
childHeight : 25,
children : [],
values : [], // objects containing x and y values
_values : [], // serialized floats alternating between x and y
numChildren : 1,
usePhysics : true,
friction : .9,
activeTouch : null,
maxVelocity : 10,
detectCollisions : true,
touchCount : 0,
timer : null,
fps : 30,
outputInitialValues: true,
rainbow: function() {
//console.log("RAINBOW", this.children.length);
for(var i = 0; i < this.children.length; i++) {
var child = this.children[i];
child.fill = Interface.XY.colors[i % Interface.XY.colors.length]; //'rgba('+Math.round(Math.random()*255)+','+Math.round(Math.random()*255)+','+Math.round(Math.random()*255)+',.2)';
//console.log("YUM", child.fill)
}
//this.refresh()
},
remove: function() { this.stopAnimation(); Interface.widgets.splice( Interface.widgets.indexOf( this ), 1 ); },
add : function() { if(this.usePhysics) this.startAnimation(); },
startAnimation : function() {
if(this.timer === null) {
this.timer = setInterval( function() { self.refresh(); }, (1 / this.fps) * 1000);
}
},
stopAnimation : function() { clearInterval(this.timer); this.timer = null; },
animate : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height = this._height(),
shouldrunvaluechange = false;
for(var i = 0; i < this.children.length; i++) {
var moveX = moveY = false,
child = this.children[i];
if(child.x + child.vx < width && child.x + child.vx > 0) {
child.x += child.vx;
}else{
if(child.x + child.vx >= width && child.vx > 0 ) {
child.vx *= -1;
}else if(child.x + child.vx <= 0 && child.vx < 0) {
child.vx *= -1;
}else{
child.x += child.vx;
}
}
if(child.y + child.vy < height && child.y + child.vy > 0) {
child.y += child.vy;
}else{
if(child.y + child.vy >= height && child.vy > 0 ) {
child.vy *= -1;
}else if(child.y + child.vy <= 0 && child.vy < 0) {
child.vy *= -1;
}else{
child.y += child.vy;
}
}
child.vx *= this.friction;
child.vy *= this.friction;
var newValueX = child.x / width;
var newValueY = child.y / height;
var range = this.max - this.min;
if(this.values[child.id].x !== newValueX || this.values[child.id].y !== newValueY) {
this.values[child.id].x = this.min + range * newValueX;
this.values[child.id].y = this.min + range * newValueY;
shouldrunvaluechange = true;
}
if(this.detectCollisions) {
if(!child.collideFlag) {
this.collisionTest(child);
}else{
child.collideFlag = false;
}
}
child.vx = Math.abs(child.vx) > this.maxVelocity ? this.maxVelocity * sign(child.vx) : child.vx;
child.vy = Math.abs(child.vy) > this.maxVelocity ? this.maxVelocity * sign(child.vy): child.vy;
}
if(shouldrunvaluechange) {
this.sendTargetMessage();
if(this.onvaluechange) {
this.onvaluechange();
}
}
},
// MultiXY sends out all child values in serialized xy pairs
sendValues : function() {
var tt = '';
this._values.length = 0;
for(var i = 0; i < this.values.length; i++) {
tt += 'ff';
this._values.push( this.values[i].x );
this._values.push( this.values[i].y );
}
if(this.target === "OSC") {
if(Interface.OSC) {
Interface.OSC.send( this.key, tt, this._values );
}
}
},
collisionTest : function(c1) {
var cw2 = (this.childWidth * 2) * (this.childWidth * 2);
for(var i = 0; i < this.children.length; i++) {
var c2 = this.children[i];
if(c1.id !== c2.id) {
var distance = Math.pow(c1.x - c2.x, 2) + Math.pow(c1.y - c2.y, 2);
if(distance < cw2) { // avoid square root by raising the distance check
this.collide(c1, c2)
}
}
}
},
collide : function(c1,c2) {
// posDiff, velDiff and normal are upvalues for gc performance
posDiff.x = c1.x - c2.x;
posDiff.y = c1.y - c2.y;
velDiff.x = c1.vx - c2.vx;
velDiff.y = c1.vy - c2.vy;
cDot = Math.sqrt( Math.pow(posDiff.x, 2) + Math.pow(posDiff.y, 2) );
normal.x = posDiff.x / cDot;
normal.y = posDiff.y / cDot;
var d = (normal.x * velDiff.x) + (normal.y * velDiff.y);
c2.vx = c1.vx + d * normal.x;
c2.vy = c1.vy + d * normal.y;
c1.vx = c2.vx - d * normal.x;
c1.vy = c2.vy - d * normal.y;
c2.x -= normal.x;
c2.y -= normal.y;
c1.x += normal.x;
c1.y += normal.y;
c1.vx = Math.abs(c1.vx) > this.maxVelocity ? this.maxVelocity * sign(c1.vx) : c1.vx;
c1.vy = Math.abs(c1.vy) > this.maxVelocity ? this.maxVelocity * sign(c1.vy) : c1.vy;
c2.vx = Math.abs(c2.vx) > this.maxVelocity ? this.maxVelocity * sign(c2.vx) : c2.vx;
c2.vy = Math.abs(c2.vy) > this.maxVelocity ? this.maxVelocity * sign(c2.vy) : c2.vy;
c1.collideFlag = true;
c2.collideFlag = true;
},
draw : function() {
var x = this._x(),
y = this._y(),
width = this._width(),
height= this._height();
if(this.usePhysics) this.animate();
this.ctx.fillStyle = this._background();
//this.ctx.fillRect( this.x, this.y, this.width, this.height );
this.ctx.strokeStyle = this._stroke();
//this.ctx.strokeRect( this.x, this.y, this.width, this.height );
this.ctx.save();
this.ctx.beginPath();
this.ctx.moveTo(x, y);
this.ctx.lineTo(x + width, y);
this.ctx.lineTo(x + width, y + height);
this.ctx.lineTo(x, y + height);
this.ctx.lineTo(x, y);
this.ctx.fill();
this.ctx.stroke();
this.ctx.clip();
this.ctx.fillStyle = this._fill();
for(var i = 0; i < this.children.length; i++) {
var child = this.children[i];
this.ctx.lineWidth = 2
this.ctx.fillStyle = child.fill || this._fill();
this.ctx.beginPath();
this.ctx.arc(x + child.x, y + child.y, this.childWidth, 0, Math.PI*2, true);
this.ctx.closePath();
this.ctx.fill();
this.ctx.stroke();
//this.ctx.fillRect( this.x + child.x, this.y + child.y, this.childWidth, this.childHeight);
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
this.ctx.fillStyle = this._stroke();
this.ctx.font = this._font();
this.ctx.fillText(child.id, x + child.x, y + child.y);
}
this.ctx.closePath();
this.ctx.restore();
},
changeValue : function( touch, xOffset, yOffset ) {
if(this.hasFocus || !this.requiresFocus) {
touch.x = xOffset;
if(touch.x < 0 ) touch.x = 0;
if(touch.x > this._width()) touch.x = this._width();
touch.y = yOffset;// - this.half;
if(touch.y < 0) touch.y = 0;
if(touch.y > this._height()) touch.y = this._height();
this.values[touch.id].x = xOffset / this._width();
this.values[touch.id].y = yOffset / this._height();
if(this.onvaluechange) this.onvaluechange();
if(!this.usePhysics) {
this.sendTargetMessage();
this.refresh();
}
}
},
makeChildren : function() {
for(var i = 0; i < this.numChildren; i++) {
var x = Math.random() * this._width(),
y = Math.random() * this._height()
this.children.push({ id:i, x:x, y:y, vx:0, vy:0, collideFlag:false, isActive:false, lastPosition:null, });
this.values.push({ x:null, y:null });
}
},
touchEvent : function(touch) {
var isHit = this.hitTest(touch);
var touchMouseName = convertTouchEvent(touch.type);
if(isHit) {
if(touch.type === 'touchstart') {
this.hasFocus = true;
this.touchCount++;
this.trackTouch(touch.x - this._x(), touch.y - this._y(), touch);
}else{
if(this[touch.type])
this[touch.type](touch, isHit, touch.childID); // normal event
}
if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event
if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event
}else if(touch.type === 'touchend'){
this.touchCount--;
if(this.touchCount === 0) {
this.hasFocus = false;
}else if(this.touchCount < 0 ) {
this.touchCount = 0;
}
this.touchend(touch)
if(this['on'+touch.type]) this['on'+touch.type](touch, isHit, touch.childId); // user defined event
if(this['on'+touchMouseName]) this['on'+touchMouseName](touch, isHit); // user defined event
}
},
trackMouse : function(xPos, yPos, id) {
var closestDiff = 10000,
touchFound = null,
touchNum = null;
for(var i = 0; i < this.children.length; i++) {
var touch = this.children[i],
xdiff = Math.abs(touch.x - xPos),
ydiff = Math.abs(touch.y - yPos);
if(xdiff + ydiff < closestDiff) {
closestDiff = xdiff + ydiff;
touchFound = touch;
touchNum = i;
}
}
touchFound.isActive = true;
touchFound.vx = 0;
touchFound.vy = 0;
if(touchFound != null) {
this.changeValue(touchFound, xPos, yPos);
}
this.activeTouch = touchFound;
this.activeTouch.lastTouch = null;
this.lastTouched = touchFound;
},
mousedown : function(e) {
if(this.hitTest(e)) {
this.trackMouse(e.x - this._x(), e.y - this._y());
}
},
mousemove : function(e) {
if(this.hitTest(e) && this.activeTouch !== null) {
if(this.activeTouch.lastTouch === null) {
this.activeTouch.lastTouch = {x:e.x - this._x(), y:e.y - this._y()};
}else{
var now = {x:e.x - this._x(), y:e.y - this._y()};
this.activeTouch.velocity = {x:now.x - this.activeTouch.lastTouch.x, y:now.y - this.activeTouch.lastTouch.y };
this.activeTouch.lastTouch = now;
}
this.changeValue(this.activeTouch, e.x - this._x(), e.y - this._y());
}
},
mouseup : function(e) {
if(this.activeTouch !== null) {
this.activeTouch.vx = this.activeTouch.velocity.x;
this.activeTouch.vy = this.activeTouch.velocity.y;
this.activeTouch.lastTouch = null;
this.activeTouch = null;
}
for(var i = 0; i < this.children.length; i++) {
this.children[i].isActive = false;
}
},
trackTouch : function(xPos, yPos, _touch) {
var closestDiff = 10000;
var touchFound = null;
var touchNum = null;
for(var i = 0; i < this.children.length; i++) {
var touch = this.children[i];
var xdiff = Math.abs(touch.x - xPos);
var ydiff = Math.abs(touch.y - yPos);
if(xdiff + ydiff < closestDiff && !touch.isActive) {
closestDiff = xdiff + ydiff;
touchFound = touch;
touchNum = i;
}
}
touchFound.isActive = true;
touchFound.vx = 0;
touchFound.vy = 0;
touchFound.identifier = _touch.identifier;
touchFound.childID = touchNum;
if(touchFound != null)
this.changeValue(touchFound, xPos, yPos);
this.lastTouched = touchFound;
return touchFound.childID;
},
touchstart : function(touch) {
// if(this.hitTest(touch)) {
// this.trackTouch(touch.x - this.x, touch.y - this.y, touch);
// }
},
touchmove : function(touch) {
for(var t = 0; t < this.children.length; t++) {
_t = this.children[t];
if(touch.identifier === _t.identifier) {
this.changeValue(_t, touch.x - this._x(), touch.y - this._y());
var now = {x:touch.x - this._x(), y:touch.y - this._y()};
if(_t.lastPosition !== null) {
_t.velocity = {x:now.x - _t.lastPosition.x, y:now.y - _t.lastPosition.y };
}
_t.lastPosition = now;
}
}
},
touchend : function(touch) {
var found = false;
var tu = null;
for(var t = 0; t < this.children.length; t++) {
var _t = this.children[t];
if(touch.identifier === _t.identifier) {
if( _t.velocity ) {
_t.vx = _t.velocity.x;
_t.vy = _t.velocity.y;
}
_t.lastPosition = null;
_t.isActive = false;
found = true;
tu = t.childID;
}
}
if(found) { this.touchUp = tu; }
//if(!found) console.log("NOT FOUND", touch.identifier);
},
_init : function() {
this.makeChildren();
if( this.outputInitialValues ) {
this.sendTargetMessage();
}
},
})
.init( arguments[0] );
this.requiresFocus = false; // is a widget default... must set after init.
this.half = this.childWidth / 2;
var numChildren = this.numChildren;
Object.defineProperty(this, 'numChildren', {
get : function() { return numChildren; },
set : function(_numChildren) {
var temp = _numChildren;
while(_numChildren > numChildren) {
this.children.push({ id:this.children.length, x:Math.random() * this._width(), y:Math.random() * this._height(), vx:0, vy:0, collideFlag:false, isActive:false, lastPosition:null, });
this.values.push({ x:null, y: null});
numChildren++;
}
while(_numChildren < numChildren) {
this.chidren.pop();
this.values.pop();
numChildren--;
}
this.refresh();
numChildren = _numChildren;
}
});
};
Interface.XY.prototype = Interface.Widget;
Interface.XY.colors = [
'rgba(255,0,0,.35)',
'rgba(0,255,0,.35)',
'rgba(0,0,255,.35)',
'rgba(0,255,255,.35)',
'rgba(255,0,255,.35)',
'rgba(255,255,0,.35)',
];
/**#Interface.Menu - Widget
A multi-option dropdown menu.
## Example Usage##
`a = new Interface.Menu({x:0, y:0, options:['red', 'yellow', 'green'] });
a.onvaluechange = function() { b.background = this.value; }
b = new Interface.Slider({x:.5, y:.5, width:.2, height:.3});
panel = new Interface.Panel();
panel.add(a,b);
`
## Constructor
**param** *properties*: Object. A dictionary of property values (see below) to set for the menu on initialization.
- - - -
**/
/**###Interface.Menu.options : property
Array. A list of values found in the menu.
**/
/**###Interface.Menu.css : property
Object. A dictionary of css keys / values to be applied to the menu.
**/
/**###Interface.Menu.onvaluechange : method
The event handler fired whenever the selected menu option changes.
param **newValue** Number or String. The new menu value.
param **oldValue** Number or String. The previous menu value.
**/
Interface.Menu = function() {
Interface.extend(this, {
type : 'Menu',
_value: 0,
serializeMe : ["options", "fontSize"],
options: [],
fontSize:15,
touchEvent: function(e) { // we have to simulate this since the actual event was cancelled to avoid scrolling behavior
if(this.hitTest(e)) {
e.stopPropagation();
/*var evt = document.createEvent('TouchEvent');
evt.initUIEvent('touchstart', true, true);
evt.view = window;
evt.screenX = e.screenX;
evt.screenY = e.screenY;
evt.clientX = e.clientX;
evt.clientY = e.clientY;
evt.bubbles = false;
evt.view = window;
evt.altKey = false;
evt.ctrlKey = false;
evt.shiftKey = false;
evt.metaKey = false;
this.element.dispatchEvent(evt);*/
}
},
_init : function() {
this.element = $("