* var div0 = createDiv('this is the parent');
* var div1 = createDiv('this is the child');
* div0.child(div1); // use p5.Element
*
*
* var div0 = createDiv('this is the parent');
* var div1 = createDiv('this is the child');
* div1.id('apples');
* div0.child('apples'); // use id
*
*
* var div0 = createDiv('this is the parent');
* var elt = document.getElementById('myChildDiv');
* div0.child(elt); // use element from page
*
*/
p5.Element.prototype.child = function(c) {
if (typeof c === 'string') {
c = document.getElementById(c);
} else if (c instanceof p5.Element) {
c = c.elt;
}
this.elt.appendChild(c);
return this;
};
/**
*
* If an argument is given, sets the inner HTML of the element,
* replacing any existing html. If no arguments are given, returns
* the inner HTML of the element.
*
* @for p5.Element
* @method html
* @param {String} [html] the HTML to be placed inside the element
* @return {Object/p5.Element|String}
*/
p5.Element.prototype.html = function(html) {
if (typeof html !== 'undefined') {
this.elt.innerHTML = html;
return this;
} else {
return this.elt.innerHTML;
}
};
/**
*
* Sets the position of the element relative to (0, 0) of the
* window. Essentially, sets position:absolute and left and top
* properties of style. If no arguments given returns the x and y position
* of the element in an object.
*
* @method position
* @param {Number} [x] x-position relative to upper left of window
* @param {Number} [y] y-position relative to upper left of window
* @return {Object/p5.Element}
* @example
*
* function setup() {
* var cnv = createCanvas(100, 100);
* // positions canvas 50px to the right and 100px
* // below upper left corner of the window
* cnv.position(50, 100);
* }
*
*/
p5.Element.prototype.position = function() {
if (arguments.length === 0){
return { 'x' : this.elt.offsetLeft , 'y' : this.elt.offsetTop };
}else{
this.elt.style.position = 'absolute';
this.elt.style.left = arguments[0]+'px';
this.elt.style.top = arguments[1]+'px';
this.x = arguments[0];
this.y = arguments[1];
return this;
}
};
/**
* Translates an element with css transforms in either 2d (if 2 arguments given)
* or 3d (if 3 arguments given) space.
* @method translate
* @param {Number} x x-position in px
* @param {Number} y y-position in px
* @param {Number} [z] z-position in px
* @param {Number} [perspective] sets the perspective of the parent element in px,
* default value set to 1000px
* @return {Object/p5.Element}
* @example
*
* function setup() {
* createCanvas(100,100);
* //translates canvas 50px down
* select('canvas').translate(0,50);
* }
*
*/
p5.Element.prototype.translate = function(){
this.elt.style.position = 'absolute';
if (arguments.length === 2){
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate('+arguments[0]+'px, '+arguments[1]+'px)';
this.elt.style.transform += style;
}else if (arguments.length === 3){
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)';
this.elt.style.transform += style;
this.elt.parentElement.style.perspective = '1000px';
}else if (arguments.length === 4){
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate3d('+arguments[0]+'px,'+arguments[1]+'px,'+arguments[2]+'px)';
this.elt.style.transform += style;
this.elt.parentElement.style.perspective = arguments[3]+'px';
}
return this;
};
/**
* Rotates an element with css transforms in either 2d (if 2 arguments given)
* or 3d (if 3 arguments given) space.
* @method rotate
* @param {Number} x amount of degrees to rotate the element along the x-axis in deg
* @param {Number} [y] amount of degrees to rotate the element along the y-axis in deg
* @param {Number} [z] amount of degrees to rotate the element along the z-axis in deg
* @return {Object/p5.Element}
* @example
*
* var x = 0,
* y = 0,
* z = 0;
* function setup(){
* createCanvas(100,100);
* }
* function draw(){
* x+=.5 % 360;
* y+=.5 % 360;
* z+=.5 % 360;
* //rotates the canvas .5deg (degrees) on every axis each frame.
* select('canvas').rotate(x,y,z);
* }
*
*/
p5.Element.prototype.rotate = function(){
if (arguments.length === 1){
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotate('+arguments[0]+'deg)';
this.elt.style.transform += style;
}else if (arguments.length === 2){
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotate('+arguments[0]+'deg, '+arguments[1]+'deg)';
this.elt.style.transform += style;
}else if (arguments.length === 3){
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotateX('+arguments[0]+'deg)';
this.elt.style.transform += 'rotateY('+arguments[1]+'deg)';
this.elt.style.transform += 'rotateZ('+arguments[2]+'deg)';
this.elt.style.transform += style;
}
return this;
};
/**
* Sets the given style (css) property (1st arg) of the element with the
* given value (2nd arg). If a single argument is given, .style()
* returns the value of the given property; however, if the single argument
* is given in css syntax ('text-align:center'), .style() sets the css
* appropriatly. .style() also handles 2d and 3d css transforms. If
* the 1st arg is 'rotate', 'translate', or 'position', the following arguments
* accept Numbers as values. ('translate', 10, 100, 50);
*
* @method style
* @param {String} property property to be set
* @param {String|Number} [value] value to assign to property
* @param {String|Number} [value] value to assign to property (rotate/translate)
* @param {String|Number} [value] value to assign to property (rotate/translate)
* @param {String|Number} [value] value to assign to property (translate)
* @return {String|Object/p5.Element} value of property, if no value is specified
* or p5.Element
* @example
*
* var myDiv = createDiv("I like pandas.");
* myDiv.style("color", "#ff0000");
* myDiv.style("font-size", "18px");
*
*/
p5.Element.prototype.style = function(prop, val) {
var self = this;
if (typeof val === 'undefined') {
if (prop.indexOf(':') === -1) {
var styles = window.getComputedStyle(self.elt);
var style = styles.getPropertyValue(prop);
return style;
} else {
var attrs = prop.split(';');
for (var i = 0; i < attrs.length; i++) {
var parts = attrs[i].split(':');
if (parts[0] && parts[1]) {
this.elt.style[parts[0].trim()] = parts[1].trim();
}
}
}
} else {
if (prop === 'rotate'){
if (arguments.length === 2) {
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotate(' + arguments[0] + 'deg)';
this.elt.style.transform += style;
} else if (arguments.length === 3) {
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotate(' + arguments[0] + 'deg, ' + arguments[1] + 'deg)';
this.elt.style.transform += style;
} else if (arguments.length === 4) {
var style = this.elt.style.transform.replace(/rotate3d\(.*\)/g, '');
style = style.replace(/rotate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'rotateX(' + arguments[0] + 'deg)';
this.elt.style.transform += 'rotateY(' + arguments[1] + 'deg)';
this.elt.style.transform += 'rotateZ(' + arguments[2] + 'deg)';
this.elt.style.transform += style;
}
} else if (prop === 'translate') {
if (arguments.length === 3) {
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate(' + arguments[0] + 'px, ' + arguments[1] + 'px)';
this.elt.style.transform += style;
} else if (arguments.length === 4) {
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate3d(' + arguments[0] + 'px,' + arguments[1] + 'px,' + arguments[2] + 'px)';
this.elt.style.transform += style;
this.elt.parentElement.style.perspective = '1000px';
} else if (arguments.length === 5) {
var style = this.elt.style.transform.replace(/translate3d\(.*\)/g, '');
style = style.replace(/translate[X-Z]?\(.*\)/g, '');
this.elt.style.transform = 'translate3d(' + arguments[0] + 'px,' + arguments[1] + 'px,' + arguments[2] + 'px)';
this.elt.style.transform += style;
this.elt.parentElement.style.perspective = arguments[3] + 'px';
}
} else if (prop === 'position') {
this.elt.style.left = arguments[1] + 'px';
this.elt.style.top = arguments[2] + 'px';
this.x = arguments[1];
this.y = arguments[2];
} else {
this.elt.style[prop] = val;
if (prop === 'width' || prop === 'height' || prop === 'left' || prop === 'top') {
var numVal = val.replace(/\D+/g, '');
this[prop] = parseInt(numVal, 10);
}
}
}
return this;
};
/**
*
* Adds a new attribute or changes the value of an existing attribute
* on the specified element. If no value is specified, returns the
* value of the given attribute, or null if attribute is not set.
*
* @method attribute
* @param {String} attr attribute to set
* @param {String} [value] value to assign to attribute
* @return {String|Object/p5.Element} value of attribute, if no value is
* specified or p5.Element
* @example
*
* var myDiv = createDiv("I like pandas.");
*myDiv.attribute("align", "center");
*
*/
p5.Element.prototype.attribute = function(attr, value) {
if (typeof value === 'undefined') {
return this.elt.getAttribute(attr);
} else {
this.elt.setAttribute(attr, value);
return this;
}
};
/**
* Either returns the value of the element if no arguments
* given, or sets the value of the element.
*
* @method value
* @param {String|Number} [value]
* @return {String|Object/p5.Element} value of element if no value is specified or p5.Element
*/
p5.Element.prototype.value = function() {
if (arguments.length > 0) {
this.elt.value = arguments[0];
return this;
} else {
if (this.elt.type === 'range') {
return parseFloat(this.elt.value);
}
else return this.elt.value;
}
};
/**
*
* Shows the current element. Essentially, setting display:block for the style.
*
* @method show
* @return {Object/p5.Element}
*/
p5.Element.prototype.show = function() {
this.elt.style.display = 'block';
return this;
};
/**
* Hides the current element. Essentially, setting display:none for the style.
*
* @method hide
* @return {Object/p5.Element}
*/
p5.Element.prototype.hide = function() {
this.elt.style.display = 'none';
return this;
};
/**
*
* Sets the width and height of the element. AUTO can be used to
* only adjust one dimension. If no arguments given returns the width and height
* of the element in an object.
*
* @method size
* @param {Number} [w] width of the element
* @param {Number} [h] height of the element
* @return {Object/p5.Element}
*/
p5.Element.prototype.size = function(w, h) {
if (arguments.length === 0){
return { 'width' : this.elt.offsetWidth , 'height' : this.elt.offsetHeight };
}else{
var aW = w;
var aH = h;
var AUTO = p5.prototype.AUTO;
if (aW !== AUTO || aH !== AUTO) {
if (aW === AUTO) {
aW = h * this.width / this.height;
} else if (aH === AUTO) {
aH = w * this.height / this.width;
}
// set diff for cnv vs normal div
if (this.elt instanceof HTMLCanvasElement) {
var j = {};
var k = this.elt.getContext('2d');
for (var prop in k) {
j[prop] = k[prop];
}
this.elt.setAttribute('width', aW * this._pInst._pixelDensity);
this.elt.setAttribute('height', aH * this._pInst._pixelDensity);
this.elt.setAttribute('style', 'width:' + aW + 'px; height:' + aH + 'px');
this._pInst.scale(this._pInst._pixelDensity, this._pInst._pixelDensity);
for (var prop in j) {
this.elt.getContext('2d')[prop] = j[prop];
}
} else {
this.elt.style.width = aW+'px';
this.elt.style.height = aH+'px';
this.elt.width = aW;
this.elt.height = aH;
this.width = aW;
this.height = aH;
}
this.width = this.elt.offsetWidth;
this.height = this.elt.offsetHeight;
if (this._pInst) { // main canvas associated with p5 instance
if (this._pInst._curElement.elt === this.elt) {
this._pInst._setProperty('width', this.elt.offsetWidth);
this._pInst._setProperty('height', this.elt.offsetHeight);
}
}
}
return this;
}
};
/**
* Removes the element and deregisters all listeners.
* @method remove
* @example
*
* var myDiv = createDiv('this is some text');
* myDiv.remove();
*
*/
p5.Element.prototype.remove = function() {
// deregister events
for (var ev in this._events) {
this.elt.removeEventListener(ev, this._events[ev]);
}
if (this.elt.parentNode) {
this.elt.parentNode.removeChild(this.elt);
}
delete(this);
};
// =============================================================================
// p5.MediaElement additions
// =============================================================================
/**
* Extends p5.Element to handle audio and video. In addition to the methods
* of p5.Element, it also contains methods for controlling media. It is not
* called directly, but p5.MediaElements are created by calling createVideo,
* createAudio, and createCapture.
*
* @class p5.MediaElement
* @constructor
* @param {String} elt DOM node that is wrapped
* @param {Object} [pInst] pointer to p5 instance
*/
p5.MediaElement = function(elt, pInst) {
p5.Element.call(this, elt, pInst);
this._prevTime = 0;
this._cueIDCounter = 0;
this._cues = [];
this.pixelDensity = 1;
};
p5.MediaElement.prototype = Object.create(p5.Element.prototype);
/**
* Play an HTML5 media element.
*
* @method play
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.play = function() {
if (this.elt.currentTime === this.elt.duration) {
this.elt.currentTime = 0;
}
if (this.elt.readyState > 1) {
this.elt.play();
} else {
// in Chrome, playback cannot resume after being stopped and must reload
this.elt.load();
this.elt.play();
}
return this;
};
/**
* Stops an HTML5 media element (sets current time to zero).
*
* @method stop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.stop = function() {
this.elt.pause();
this.elt.currentTime = 0;
return this;
};
/**
* Pauses an HTML5 media element.
*
* @method pause
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.pause = function() {
this.elt.pause();
return this;
};
/**
* Set 'loop' to true for an HTML5 media element, and starts playing.
*
* @method loop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.loop = function() {
this.elt.setAttribute('loop', true);
this.play();
return this;
};
/**
* Set 'loop' to false for an HTML5 media element. Element will stop
* when it reaches the end.
*
* @method noLoop
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.noLoop = function() {
this.elt.setAttribute('loop', false);
return this;
};
/**
* Set HTML5 media element to autoplay or not.
*
* @method autoplay
* @param {Boolean} autoplay whether the element should autoplay
* @return {Object/p5.Element}
*/
p5.MediaElement.prototype.autoplay = function(val) {
this.elt.setAttribute('autoplay', val);
return this;
};
/**
* Sets volume for this HTML5 media element. If no argument is given,
* returns the current volume.
*
* @param {Number} [val] volume between 0.0 and 1.0
* @return {Number|p5.MediaElement} current volume or p5.MediaElement
* @method volume
*/
p5.MediaElement.prototype.volume = function(val) {
if (typeof val === 'undefined') {
return this.elt.volume;
} else {
this.elt.volume = val;
}
};
/**
* If no arguments are given, returns the current time of the elmeent.
* If an argument is given the current time of the element is set to it.
*
* @method time
* @param {Number} [time] time to jump to (in seconds)
* @return {Number|Object/p5.MediaElement} current time (in seconds)
* or p5.MediaElement
*/
p5.MediaElement.prototype.time = function(val) {
if (typeof val === 'undefined') {
return this.elt.currentTime;
} else {
this.elt.currentTime = val;
}
};
/**
* Returns the duration of the HTML5 media element.
*
* @method duration
* @return {Number} duration
*/
p5.MediaElement.prototype.duration = function() {
return this.elt.duration;
};
p5.MediaElement.prototype.pixels = [];
p5.MediaElement.prototype.loadPixels = function() {
if (this.loadedmetadata) { // wait for metadata for w/h
if (!this.canvas) {
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.drawingContext = this.canvas.getContext('2d');
}
this.drawingContext.drawImage(this.elt, 0, 0, this.width, this.height);
p5.Renderer2D.prototype.loadPixels.call(this);
}
return this;
}
p5.MediaElement.prototype.updatePixels = function(x, y, w, h){
if (this.loadedmetadata) { // wait for metadata
p5.Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
}
return this;
}
p5.MediaElement.prototype.get = function(x, y, w, h){
if (this.loadedmetadata) { // wait for metadata
return p5.Renderer2D.prototype.get.call(this, x, y, w, h);
} else return [0, 0, 0, 255];
};
p5.MediaElement.prototype.set = function(x, y, imgOrCol){
if (this.loadedmetadata) { // wait for metadata
p5.Renderer2D.prototype.set.call(this, x, y, imgOrCol);
}
};
/*** CONNECT TO WEB AUDIO API / p5.sound.js ***/
/**
* Send the audio output of this element to a specified audioNode or
* p5.sound object. If no element is provided, connects to p5's master
* output. That connection is established when this method is first called.
* All connections are removed by the .disconnect() method.
*
* This method is meant to be used with the p5.sound.js addon library.
*
* @method connect
* @param {AudioNode|p5.sound object} audioNode AudioNode from the Web Audio API,
* or an object from the p5.sound library
*/
p5.MediaElement.prototype.connect = function(obj) {
var audioContext, masterOutput;
// if p5.sound exists, same audio context
if (typeof p5.prototype.getAudioContext === 'function') {
audioContext = p5.prototype.getAudioContext();
masterOutput = p5.soundOut.input;
} else {
try {
audioContext = obj.context;
masterOutput = audioContext.destination
} catch(e) {
throw 'connect() is meant to be used with Web Audio API or p5.sound.js'
}
}
// create a Web Audio MediaElementAudioSourceNode if none already exists
if (!this.audioSourceNode) {
this.audioSourceNode = audioContext.createMediaElementSource(this.elt);
// connect to master output when this method is first called
this.audioSourceNode.connect(masterOutput);
}
// connect to object if provided
if (obj) {
if (obj.input) {
this.audioSourceNode.connect(obj.input);
} else {
this.audioSourceNode.connect(obj);
}
}
// otherwise connect to master output of p5.sound / AudioContext
else {
this.audioSourceNode.connect(masterOutput);
}
};
/**
* Disconnect all Web Audio routing, including to master output.
* This is useful if you want to re-route the output through
* audio effects, for example.
*
* @method disconnect
*/
p5.MediaElement.prototype.disconnect = function() {
if (this.audioSourceNode) {
this.audioSourceNode.disconnect();
} else {
throw 'nothing to disconnect';
}
};
/*** SHOW / HIDE CONTROLS ***/
/**
* Show the default MediaElement controls, as determined by the web browser.
*
* @method showControls
*/
p5.MediaElement.prototype.showControls = function() {
// must set style for the element to show on the page
this.elt.style['text-align'] = 'inherit';
this.elt.controls = true;
};
/**
* Hide the default mediaElement controls.
*
* @method hideControls
*/
p5.MediaElement.prototype.hideControls = function() {
this.elt.controls = false;
};
/*** SCHEDULE EVENTS ***/
/**
* Schedule events to trigger every time a MediaElement
* (audio/video) reaches a playback cue point.
*
* Accepts a callback function, a time (in seconds) at which to trigger
* the callback, and an optional parameter for the callback.
*
* Time will be passed as the first parameter to the callback function,
* and param will be the second parameter.
*
*
* @method addCue
* @param {Number} time Time in seconds, relative to this media
* element's playback. For example, to trigger
* an event every time playback reaches two
* seconds, pass in the number 2. This will be
* passed as the first parameter to
* the callback function.
* @param {Function} callback Name of a function that will be
* called at the given time. The callback will
* receive time and (optionally) param as its
* two parameters.
* @param {Object} [value] An object to be passed as the
* second parameter to the
* callback function.
* @return {Number} id ID of this cue,
* useful for removeCue(id)
* @example
*
* function setup() {
* background(255,255,255);
*
* audioEl = createAudio('assets/beat.mp3');
* audioEl.showControls();
*
* // schedule three calls to changeBackground
* audioEl.addCue(0.5, changeBackground, color(255,0,0) );
* audioEl.addCue(1.0, changeBackground, color(0,255,0) );
* audioEl.addCue(2.5, changeBackground, color(0,0,255) );
* audioEl.addCue(3.0, changeBackground, color(0,255,255) );
* audioEl.addCue(4.2, changeBackground, color(255,255,0) );
* audioEl.addCue(5.0, changeBackground, color(255,255,0) );
* }
*
* function changeBackground(val) {
* background(val);
* }
*
*/
p5.MediaElement.prototype.addCue = function(time, callback, val) {
var id = this._cueIDCounter++;
var cue = new Cue(callback, time, id, val);
this._cues.push(cue);
if (!this.elt.ontimeupdate) {
this.elt.ontimeupdate = this._onTimeUpdate.bind(this);
}
return id;
};
/**
* Remove a callback based on its ID. The ID is returned by the
* addCue method.
*
* @method removeCue
* @param {Number} id ID of the cue, as returned by addCue
*/
p5.MediaElement.prototype.removeCue = function(id) {
for (var i = 0; i < this._cues.length; i++) {
var cue = this._cues[i];
if (cue.id === id) {
this.cues.splice(i, 1);
}
}
if (this._cues.length === 0) {
this.elt.ontimeupdate = null
}
};
/**
* Remove all of the callbacks that had originally been scheduled
* via the addCue method.
*
* @method clearCues
*/
p5.MediaElement.prototype.clearCues = function() {
this._cues = [];
this.elt.ontimeupdate = null;
};
// private method that checks for cues to be fired if events
// have been scheduled using addCue(callback, time).
p5.MediaElement.prototype._onTimeUpdate = function() {
var playbackTime = this.time();
for (var i = 0 ; i < this._cues.length; i++) {
var callbackTime = this._cues[i].time;
var val = this._cues[i].val;
if (this._prevTime < callbackTime && callbackTime <= playbackTime) {
// pass the scheduled callbackTime as parameter to the callback
this._cues[i].callback(val);
}
}
this._prevTime = playbackTime;
};
// Cue inspired by JavaScript setTimeout, and the
// Tone.js Transport Timeline Event, MIT License Yotam Mann 2015 tonejs.org
var Cue = function(callback, time, id, val) {
this.callback = callback;
this.time = time;
this.id = id;
this.val = val;
};
// =============================================================================
// p5.File
// =============================================================================
/**
* Base class for a file
* Using this for createFileInput
*
* @class p5.File
* @constructor
* @param {File} file File that is wrapped
* @param {Object} [pInst] pointer to p5 instance
*/
p5.File = function(file, pInst) {
/**
* Underlying File object. All normal File methods can be called on this.
*
* @property file
*/
this.file = file;
this._pInst = pInst;
// Splitting out the file type into two components
// This makes determining if image or text etc simpler
var typeList = file.type.split('/');
/**
* File type (image, text, etc.)
*
* @property type
*/
this.type = typeList[0];
/**
* File subtype (usually the file extension jpg, png, xml, etc.)
*
* @property subtype
*/
this.subtype = typeList[1];
/**
* File name
*
* @property name
*/
this.name = file.name;
/**
* File size
*
* @property size
*/
this.size = file.size;
// Data not loaded yet
this.data = undefined;
};
}));