/*
*Copyright (c) 2009, TellurianRing.com
*All rights reserved.
*
*Redistribution and use in source and binary forms, with or without modification,
*are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the Organization (TellurianRing.com) nor the names of
* its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
*ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
*WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
*ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
*(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
*(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
*SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @extends Point
* @class Represents a Rectangular area with x,y coordinates as well as width,
* and height.
* @author Eric Berry
* @example
* // Creates a new Bounds object using standard JSON.
* var myBounds = Bounds({x: 10, y: 10, w: 100, h: 100});
* @example Alerts true
* alert(myBounds.contains(Bounds({x: 15, y: 15})));
* @example Alerts true
* alert(myBounds.contains({x: 20, y: 20, w: 20, h: 20}));
* @example Alerts false
* alert(myBounds.contains({x: 20, y: 20, w: 100, h: 100}));
* This example alerts false, because when width and height are supplied, the
* given bounds must be completely within the 'myBounds' area.
* @example // Using a Bounds object.
* var myBounds = Bounds({x: 10, y: 10, w: 100, h: 100});
* // alerts 10
* alert(myBounds.x);
* // alerts 200
* alerts(myBounds.w * myBounds.h);
* @param {Bounds,Point,Object} _bounds Another Bounds object, or Point object. Can be created
* using this function or created with a JSON Object containing the
* following attributes:
*
*
Property
Required
Default
*
x
yes
*
y
yes
*
w
yes
*
h
yes
*
*/
function Bounds(_details) { // {{{
/** @constructs */
function _Bounds(_details) { // {{{
/**
* Height
* @field
*/
this.h = _details.h;
/**
* Width
* @field
*/
this.w = _details.w;
/**
* Checks to see if the given Bounds object is within this
* bounds object. This function works with Point objects as well.
* Meaning that if you pass it an object with only x, and y
* coordinates, it only checks to see if they are within this bounds.
* @function
*/
this.contains = function(_bounds) {
// my maximum x and y coords.
var mx = this.x + this.w;
var my = this.y + this.h;
// other bounds x and y coords.
var ox = _bounds.x;
var oy = _bounds.y;
// initial checks to make sure other x, and y coords are within these
// bounds.
if(ox < this.x || ox > mx || oy < this.y || oy > my) {
return false;
}
// if there's a width and height in the given bounds, then test to see
// if entire bounds fits within this bounds.
if(exists(_bounds.w, _bounds.h)) {
// we only need to check that the max x, and y coords of the other
// bounds is less than our max x and y coords because the other
// x and y values have already been checked, and wouldn't have
// reached here if they weren't inside this bounds.
ox = ox + _bounds.w;
oy = oy + _bounds.h;
if(ox > mx) {
return false;
}
if(oy > my) {
return false;
}
}
// if we've gotten this far, then the other bounds is within these
// bounds
return true;
}
} // }}} _Bounds
Point(_details).extend(_Bounds);
var theBounds = new _Bounds(_details);
return theBounds;
} // }}} Bounds
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Represents a CSS Font.
* @author Eric Berry
* @example
* // Creates a new Font object
* var myFont = Font({style: "bold", size: "12px", family: "Tahoma"});
* @example
* // Creates a new Font with multiple families.
* var myFont = Font({style: "bold", size: "12px", family: ["Tahoma", "Arial"]});
* @example
* // Creates a new Font using convienence method.
* var myFont = font("Tahoma", "12px", "bold");
* @param _details An JSON object containing the following properties:
*
*
Property
Required
Default
*
family
no
Tahoma
*
size
no
12px
*
style
no
Font.Styles.normal
*
variant
no
normal
*
weight
no
Font.Weights.normal
*
* @see
* Font for more details on the CSS font property.
*/
function Font(_details) { // {{{
/** @constructs */
function _Font(_details) { // {{{
this.family = check(_details.family, "Tahoma");
this.size = check(_details.size, "12px");
this.style = check(_details.style, "normal");
this.variant = check(_details.variant, "normal");
this.weight = check(_details.weight, "normal");
// toCss Creates a CSS font string from the given details.
/**
* Creates a CSS string from this Font object.
* @function
* @return A String in the format of: "[style] [variant] [weight] [size] [family(ies)]"
*/
this.toCss = function() {
var cssFont = this.style + " " + this.variant + " " + this.weight + " " + this.size
if(typeof(this.family) == 'array') {
for(var fi in this.family) {
cssFont += " " + this.family[fi];
}
} else {
cssFont += " " + this.family;
}
return cssFont;
}
} // }}} _Font
var theFont = new _Font(_details);
return theFont;
} // }}} Font
/**
*@namespace Some Font style constants
*/
Font.Styles = {
/** @property */
italic: "italic",
/** @property */
normal: "normal",
/** @property */
oblique: "oblique"
};
/**
*@namespace Some Font weight constants
*/
Font.Weights = {
/** @property */
bold: "bold",
/** @property */
bolder: "bolder",
/** @property */
lighter: "lighter",
/** @property */
normal: "normal"
};
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Groups some content together. Any action taken on a group is applied to all
* of it's children.
* @extends SceneContent
* @example // New Group where the content starts at (10,10).
* Group({
* x: 10, y: 10,
* content: [
* Graphic({ url: "./images/ball.png" }),
* Graphic({ url: "./images/beetleship.png", y: 20, x: 30 })
* ]
* })
*
*
* // In this example, the ball will display at (10,10), and the beetleship
* // will display at (50,10).
* @example // Moving the group.
* myGroup.moveBy({ y: 10 });
* // In this example, the ball will now display at (10, 20), and the beetleship
* // will display at (50, 20).
*
*
* @param {Object} _details A JSON object with extra content.
*
*
Property
Required
Default
*
content
no
new Array()
*
*/
function Group(_details) { // {{{
/** @constructs */
function _Group(_details) { // {{{
// Private Members {{{
var content = check(_details.content, new Array());
var name = _details.name;
// }}}
// Public Members {{{
/**
* Adds content to this group. Content added is first loaded then added to
* the the other content of this group at which point it will join the
* draw cycle.
* @param {SceneContent} _content The SceneContent to add to this group.
*/
this.addContent = function(_content) {
_content.load(scene, this);
content.push(_content);
}
this.draw = function(_context) {
// draw the groups contents to a different canvas.
var bounds = this.getBounds();
_context.save();
// translate the context to where the group will begin.
if(bounds.x > 0 || bounds.y > 0) {
_context.translate(bounds.x, bounds.y);
}
for(var index in content) {
var component = content[index];
if(component.isActive() && component.isVisible()) {
component.draw(_context);
}
}
_context.restore();
}
/**
* Gets the content of this group. This implementation returns the actual
* content array, but should not be used to add content to this group.
* To add content to this group use the addContent function, which loads
* the content before adding it to the content array. Using this function
* to add content to this group will result in that content not being
* loaded, but being added to the draw cycle.
* @return The Content array.
*/
this.getContent = function() { // {{{
return content;
} // }}} getContent
this.load = function(_scene, _parent) { // {{{
this.loadBase(_scene, _parent);
for(var index in content) {
var component = content[index];
component.load(_scene, this);
}
} // }}} load
this.update = function(_run_time) { // {{{
this.updateGroup(_run_time);
} // }}} update
/**
* Updates each of this groups content, and then calculates the groups
* cumulative bounds. This is mainly used by sub classes like Rotate and
* Translate groups.
*/
this.updateGroup = function(_run_time) { // {{{
var bounds = this.getBounds();
for(var index in content) {
var component = content[index];
if(component.isActive()) {
component.update(_run_time);
var comp_bounds = component.getBounds();
bounds.w = Math.max(bounds.w, comp_bounds.x + comp_bounds.w);
bounds.h = Math.max(bounds.h, comp_bounds.y + comp_bounds.h);
}
}
this.setSize(bounds);
this.updateBase();
} // }}} updateGroup
// }}} Public Members
} // }}} _Group
SceneContent(_details).extend(_Group);
var theGroup = new _Group(_details);
return theGroup;
} // }}} Group
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Describes an x and y coordinate.
* @example // Creating a point from a JSON object
* var myPoint = Point({ x: 10, y: 10 });
* @example // creating a point from another Point object, or a Bounds object.
* var myPoint2 = Point(myPoint);
* var bounds = Bounds(myPoint2);
* var myPoint3 = Point(bounds);
* @param {Point, Bounds, Object} _details Another Point object, or Bounds
* object. Can also be created with a JSON object with these attributes:
*
*
Property
Required
Default
*
x
yes
*
y
yes
*
*/
function Point(_details) { // {{{
/** @constructs */
function _Point(_details) { // {{{
var that = this;
/** @field Public details field. Contains user's original details object. */
this.details = _details;
/** @field X coordinate */
this.x = check(_details.x, 0);
/** @field Y coordinate */
this.y = check(_details.y, 0);
/**
* This extends the provided child function with this instance.
*/
this.extend = function(_child) { // {{{
_child.prototype = that;
} // }}} extend
} // }}} _Point
var thePoint = new _Point(_details);
return thePoint;
} // }}} Point
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class A Scene represents the root level of content within a Stage.
* @param {Object} _details A JSON object with the following properties:
*
*
Property
Required
Default
*
content
no
new Array()
*
name
no
""
*
* Properties:
* name: The name of this Scene.
* content: The content of this Scene.
* @example
* var myScene = Scene({
* name: "MyScene",
* content: [
* Graphic({url: "./images/ball.png"})
* ]
* });
*
*
*/
function Scene(_details) { // {{{
/** @constructs */
function _Scene(_details) { // {{{
// Private Members {{{
var content = check(_details.content, new Array());
var name = check(_details.name, "");
var stage = null;
// }}}
// Public Members {{{
this.details = _details;
/**
* Adds the given SceneContent to this Scene. This function
* calls the load function on the given SceneContent before adding it to
* the Scene's content list, so that it will be loaded before getting
* picked up by the Stage's fun loop.
* @function
* @param SceneContent _content The SceneContent to be added to this
* Scene.
*/
this.addContent = function(_content) {
_content.load(this, this);
content.push(_content);
}
/**
* Calls draw on each child content with the given canvas context.
* @function
* @param _context The drawing context from the Stage's canvas element.
*/
this.draw = function(_context) { // {{{
for(var index in content) {
var component = content[index];
if(component.isActive() && component.isVisible()) {
component.draw(_context);
}
}
} // }}} draw
/**
* This extends the provided child function with this instance.
* @function
* @param _child The child function to be extended.
*/
this.extend = function(_child) { // {{{
_child.prototype = this;
} // }}} extend
/**
* Uses Stage.getSize() to retrieve the size of the stage, and
* returns a Bounds({x: 0, y: 0, w: stage_size.w, h: stage_size.h}).
* @function
* @return Bounds({x: 0, y: 0, w: stage_size.w, h: stage_size.h})
*/
this.getBounds = function() { // {{{
var stage_size = stage.getSize();
return Bounds({x: 0, y: 0, w: stage_size.w, h: stage_size.h});
} // }}}
/**
* Always returns null. The Scene always represents the root
* level of content within a Stage.
* @function
* @return null
*/
this.getParent = function() { // {{{
return null;
} // }}}
/**
* Gets the Stage this scene belongs to.
* @function
* @return Stage object this scene belongs to.
*/
this.getStage = function() { // {{{
return stage;
} // }}} getStage
/**
* Returns a reference to itself.
* @function
* @return A reference to this Scene.
*/
this.getScene = function() { // {{{
return this;
} // }}}
/**
* Loads this Scene in the given Stage. This function loops
* through the Scene content and calls load on each component, passing a
* reference to this Scene.
* @function
* @param Stage _stage The Stage this scene belongs to.
*/
this.load = function(_stage) { // {{{
this.loadContent(_stage);
} // }}} load
/**
* Loads this Scene's content in the given Stage. This function
* loops through the Scene content and calls load on each component,
* passing a reference to this Scene.
* @function
* @param Stage _stage The Stage this scene belongs to.
*/
this.loadContent = function(_stage) { // {{{
stage = _stage;
stage[name] = this;
for(var index in content) {
var component = content[index];
component.load(this, this);
}
} // }}} load
/**
* Scene's can't be moved. This method does nothing.
* @function
*/
this.moveBy = function(_distance) { // {{{
} // }}}
/**
* Scene's can't be moved. This method does nothing.
* @function
*/
this.moveTo = function(_point) { // {{{
} // }}}
/**
* Updates this Scene with the time since the Stage loaded.
* This function loops through the Scene content and calls the update
* method on each component.
* @function
* @param _runtime The time since the Stage was loaded.
*/
this.update = function(_runtime) { // {{{
this.updateContent(_runtime);
} // }}} update
/**
* Updates this Scene's content with the time since the Stage
* loaded. This function loops through the Scene content and calls the
* update method on each component.
* @function
* @param _runtime The time since the Stage was loaded.
*/
this.updateContent = function(_runtime) { // {{{
for(var index in content) {
var component = content[index];
if(component.isActive()) {
component.update(_runtime);
}
}
} // }}} update
// }}} Public Members
} // }}} _Scene
var theScene = new _Scene(_details);
return theScene;
} // }}} Scene
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class SceneContent represents any object which can be placed in the
* "content" field of any group node of a Scene. This object defines the methods
* and attributes, which are expected to be implemented by any scene content.
* @author Eric Berry
*
* @param _details All SceneContent takes a JSON object in it's factory
* function with the following properties:
* Expected but not required properties:
*
*
Property
Required
Default
*
active
no
true
*
h
no
0
*
visible
no
true
*
w
no
0
*
x
no
0
*
y
no
0
*
name
no
*
* This is a base set of properties. Any object that extends SceneContent should
* be expected to handle this set of properties. Some exceptions include the
* Graphic object which sets the width and height properties after the image has
* been loaded. The Sound object, which doesn't have any use of coordinates or
* size. And Groups, their width and height attributes are based on their
* content.
* @example
* // Creates a new SceneContent object.
* var myContent = SceneContent({x: 10, y: 10, w: 100, h: 100, name: "my_content"});
* @example
* // Your content can be retrieved from the scene using its name.
* myScene["my content"]
* @constructs
*/
function SceneContent(_details) { // {{{
/** @constructs */
function _SceneContent(_details) { // {{{
// private variables {{{
var active = check(_details.active, true);
var h = check(_details.h, 0);
var name = _details.name;
var parent = null;
var scene = null;
var stage = null;
var visible = check(_details.visible, true);
var w = check(_details.w, 0);
var x = check(_details.x, 0);
var y = check(_details.y, 0);
// used as a prototype for the clone function below.
function Clone() {}
// }}}
// public variables {{{
/**
* The public details field should refer to the original object passed in to the
* creation function. Users should always be able to attach their own details to
* any scene content object and retrieve it again using the public details
* field.
*/
this.details = _details;
// }}}
// public functions {{{
/**
* The draw function is responsible for drawing this content to the given
* canvas context. It is called after the update function.
* @param _context The canvas context this content should be drawn to.
*/
this.draw = function(_context) {}
/**
* This extends the provided child function with this instance.
* @param _child The child function to be extended.
*/
this.extend = function(_child) { // {{{
_child.prototype = this;
} // }}} extend
/**
* The getBounds function should return the location and size of this
* content relative to it's parent.
* @example
* var myGraphic = Graphic({ x: 13, y: 13, url: "./images/ball.png" });
* var myGrp = Group({ x: 10, y: 10, content: [myGraphic] });
* alert(myGraphic.getBounds().x); // alerts 13
*
*
* @return {Bounds} with 4 fields, x, y, w, and h.
*/
this.getBounds = function() { // {{{
return Bounds({x: x, y: y, w: w, h: h});
} // }}} getBounds
/**
* Gets the name of this SceneContent as it was passed in the original
* _details object.
* @return The name of this SceneContent
*/
this.getName = function() { // {{{
return name;
} // }}} getName
/**
* The getParent function returns the parent of this content. This usually
* returns another SceneContent object, except when this object is the
* first child of the Scene object. In which case, the Scene itself is
* returned.
* @return {SceneContent|Scene} The parent of this content.
*/
this.getParent = function() { // {{{
return parent;
} // }}} getParent
/**
* The getScene function returns the scene this content belongs to. The scene
* is passed in to the load function.
* @return {Scene} The Scene Object this content belongs to.
*/
this.getScene = function() { // {{{
return scene;
} // }}} getScene
/**
* The getStage function returns the stage this content belongs to. The stage
* can originally be retrieved by the Scene object which is passed in to the
* load function.
* @return {Stage} The Stage Object this content belongs to.
*/
this.getStage = function() { // {{{
return stage;
} // }}} getStage
/**
* isActive Is this content currently active. This field tells the Scene
* that this content needs to be updated and drawn. If content is not
* active then it will not be updated or drawn.
* @function
* @returns true if this content is active, false otherwise.
*/
this.isActive = function() { // {{{
return active;
} // }}} isActive
/**
* isVisible Is this content currently visible. This field tells the Scene
* that this content needs to be drawn. If the content is not visible then
* it will not be drawn.
* @function
* @returns true if this content is visible, false otherwise.
*/
this.isVisible = function() { // {{{
return visible;
} // }}}
/**
* The load function gets called after all the stage objects and content have
* been initialized. It's only called once when the Scene's load method is
* called.
* @param {Scene} _scene The Scene this content belongs to.
* @param {SceneContent|Scene} _parent The parent of this content.
*/
this.load = function(_scene, _parent) { // {{{
this.loadBase(_scene, _parent);
} // }}} load
/**
* Sets the scene, parent and stage for this content. Also adds this object
* to the scene by name.
*/
this.loadBase = function(_scene, _parent) { // {{{
scene = _scene;
parent = _parent;
stage = scene.getStage();
scene[name] = this;
} // }}} loadBase
/**
* The moveBy function moves this content's relative x, y position by the
* the given distances. This is equivalent to an instant translation, vs.
* the use of a Translation Transform node. The function should work on
* partial data, and a lack of data should signify non-action.
* @example
* var myImage = Graphic({ x: 10, y: 10, url: "./images/ball.png" });
* alert(myImage.getBounds().x); // alerts 10
* myImage.moveBy({x: 5});
* alert(myImage.getBounds().x); // alerts 15
*
*
* @param {Number} _distance A Distance object with two fields, x, and y.
*/
this.moveBy = function(_distance) { // {{{
if(exists(_distance.x)) {
x += _distance.x;
}
if(exists(_distance.y)) {
y += _distance.y;
}
} // }}} moveBy
/**
* The moveTo function moves this content's relative x, y position to the
* values provided in the point object. The function should work on partial
* data, and a lack of data should signify non-action.
* @example
* var myImage = Graphic({ x: 10, y: 10, url: "./images/ball.png" });
* alert(myImage.getBounds().x); // alerts 10
* myImage.moveTo({x: 15});
* alert(myImage.getBounds().x); // alerts 15
*
*
* @param {Point} _point Point object with two fields, x, and y.
*/
this.moveTo = function(_point) { // {{{
if(exists(_point.x)) {
x = _point.x;
}
if(exists(_point.y)) {
y = _point.y;
}
} // }}} moveTo
/**
* setActive Sets this content's active status flag.
* @function
* @param {Boolean} _active true or false
*/
this.setActive = function(_active) { // {{{
active = _active;
} // }}} setActive
/**
* The setSize function sets this content's width and height. The function
* should work on partial data, and a lack of data should signify
* non-action.
* @example // Only updates the width of the SceneContent.
* var rect = Rectangle({ x: 10, y: 10, w: 10, h: 10 });
* rect.setSize({w: 15 });
* alert(rect.getBounds().w == 15) // alerts true.
*
*
* @param {Bounds} _dimension A Dimension object with two fields, w, and h.
*/
this.setSize = function(_dimension) { // {{{
if(exists(_dimension.w)) {
w = _dimension.w;
}
if(exists(_dimension.h)) {
h = _dimension.h;
}
} // }}} setSize
/**
* setVisible Sets this content's visible status flag.
* @function
* @param {Boolean} _visible true or false
*/
this.setVisible = function(_visible) { // {{{
visible = _visible;
} // }}} setVisible
/**
* The update function is called after the load function has been called
* and before the draw function is called. It's called repeatedly with the
* total time passed since the Scene's load time.
* @param {Number} _runtime The total time passed since the Scene's load time.
*/
this.update = function(_runtime) {
this.updateBase(_runtime);
}
/**
* The updateBase function should be called by subclasses overriding the
* update function. This function automatically hides SceneContent if it
* goes outside the boundaries of the stage. This method can be extended
* by supplying an update function in the initialization data.
* @example // Supplying your own update function
* Graphic({
* x: 10, y: 10, url: "./images/ball.png",
* update: function(_runtime) {
* // do something
* }
* })
* @param {Number} _runtime The total time passed since the Scene's load time.
*/
this.updateBase = function(_runtime) {
var bounds = this.getBounds();
var stage_size = this.getStage().getSize();
if(stage_size.contains(bounds)) {
this.setVisible(true);
} else {
this.setVisible(false);
}
if(exists(this.details.update)) {
this.details.update.apply(this, arguments);
}
}
// }}} public functions
} // }}} _SceneContent
var theSceneContent = new _SceneContent(_details);
return theSceneContent;
} // }}} SceneContent
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class The Stage function creates a new Stage and returns it.
* @param {Object} _details A JSON object with the following properties:
*
*
Property
Required
Default
*
bg_color
no
#EFEFEF
*
container_id
yes
*
height
yes
*
name
no
""
*
scene
no
Scene({name: "empty", content: []})
*
update_time
no
1
*
width
yes
*
* @example // creates an empty stage.
* var stage1 = Stage({
* width: 100, height: 100,
* container_id: "stage_example_1"
* });
*
*
* @example // Loading an additional Scene.
* var scene1 = Scene({
* content: [ Graphic({ x: 24, y: 24, url: "./images/ball.png" }) ]
* });
*
* var scene2 = Scene({
* content: [ Graphic({ x: 24, y: 20, url: "./images/beetleship.png" }) ]
* });
*
* var stage2 = Stage({
* width: 100, height: 100,
* container_id: "stage_example_2",
* scene: scene1
* });
* stage2.unload();
* stage2.setScene(scene2);
* stage2.load();
*
*
*
*/
function Stage(_details) { // {{{
/**
* @constructs A private constructor that creates a Stage object. The use of a private
* function here acts as a further abstraction to the underlying properties
* of a stage. Any modifications to the Stage function's prototype aren't
* passed down to actual Stage objects. That would require modifying the
* prototype of the _Stage function, which is inaccessable from outside. This
* doesn't prevent modifications to Stage object instances, however those
* modifications will be limited to the scope of that instance alone.
*/
function _Stage(_details) { // {{{
// Private Members {{{
// These are all private members. They have been made private so that the
// Stage isn't abnormally mutable. If properties of this Stage need to be
// modified, mutators should be created and used.
var bg_color = check(_details.bg_color, "#EFEFEF");
var canvas = null;
var container_id = _details.container_id;
var context = null;
var height = _details.height;
var load_time = null;
var name = check(_details.name, "");
var running = false;
var scene = exists(_details.scene) ? _details.scene : Scene({name: "empty", content: []});
var update_time = check(_details.update_time, 1);
var width = _details.width;
/**
* The funLoop is essentially an animation loop. It passes the time passed
* since the load time of the Stage down to the current scene, which then
* updates all of it's content.
* @private
*/
function funLoop() { // {{{
var current_time = new Date().getTime();
var run_time = current_time - load_time;
context.fillStyle = bg_color;
context.fillRect (0, 0, width, height);
if(exists(scene)) {
scene.update(run_time);
scene.draw(context);
}
if(running) {
setTimeout(funLoop, update_time);
}
} // }}} funLoop
// }}} Private Members.
// Public Members {{{
this.details = _details;
/**
* This function creates the canvas element under the container element.
* It uses the findContainer function to find the container, and uses
* document.creatElement to create the canvas element.
*/
this.createCanvas = function() { // {{{
var container = this.findContainer();
if(typeof container != 'undefined') {
canvas = createCanvas(container_id + "_canvas", width, height);
context = canvas.getContext("2d");
context.fillStyle = bg_color;
context.fillRect (0, 0, width, height);
container.appendChild(canvas);
}
} // }}} createCanvas
/**
* This function finds the container element from the Document. This
* implementation uses document.getElementById to find the container
* element.
*/
this.findContainer = function() { // {{{
return document.getElementById(container_id);
} // }}} findContainer
/**
* This function returns the name of this Stage.
*/
this.getName = function() { // {{{
return name;
} // }}} getName
/**
* Returns the size of this Stage.
*/
this.getSize = function() { // {{{
return Bounds({ x: 0, y: 0, w: width, h: height});
} // }}}
/**
* This function returns the current scene of the Stage.
*/
this.getScene = function() { // {{{
return scene;
} // }}} getScene
/**
* Checks to see if the scene is running.
* @return true if the scene is running.
*/
this.isRunning = function() { // {{{
return running;
} // }}} isRunning
/**
* This function loads this Stage, creating it's canvas and then calling
* loadScene with the current Scene if there is one.
*/
this.load = function() { // {{{
if(canvas == null) {
this.createCanvas();
}
if(exists(scene)) {
this.loadScene(scene);
}
load_time = new Date().getTime();
running = true;
funLoop();
} // }}} load
/**
* This function loads the given Scene into the Stage. It calls the load
* function of the given scene with an instance of this Stage.
* @param _scene - The Scene to load into this Stage.
*/
this.loadScene = function(_scene) { // {{{
scene = _scene;
scene.load(this);
} // }}}
/**
* Sets the scene for this stage.
*/
this.setScene = function(_scene) { // {{{
scene = _scene;
} // }}} setScene
/**
* Stops the funLoop from running and clears the FPS update.
*/
this.unload = function() {
running = false;
}
// }}} Public Members
} // }}} _Stage
// Create a Stage object with the given scene data.
var theStage = new _Stage(_details);
// if load wasn't specified in the scene definition, or it was specified as
// true, then we load the scene. Otherwise, we assume load will be called
// explicitly by the user.
if(!exists(_details.load) || _details.load) {
theStage.load();
}
return theStage;
} // }}} Stage
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
var Units = {
MILLISECONDS: 1,
MINUTES: 60000,
SECONDS: 1000,
milliseconds: 1,
minutes: 60000,
seconds: 1000
}
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* A utility function that checks to see if a given set of objects all are
* defined. This does not check against null.
* @param An undefined number of arguments.
*/
function exists() { // {{{
var exists = true;
// This loops through each argument and checks to see if it's defined.
// It shortcuts the first time it finds one that doesn't.
for(var index = 0; index < arguments.length && exists; index++) {
exists = exists && (typeof arguments[index] != 'undefined')
}
return exists;
}// }}} exists
/**
* A utility function for create a canvas element with the given ID, width, and
* height. It is not added to the document, that is left up to the caller.
* @param _id - The ID of the canvas object that will be created.
* @param _w - The width of the canvas object.
* @param _h - The height of the canvas object.
* @param _def - The default content of the canvas object. This content is shown
* in browsers that don't support the HTML 5 Canvas element.
*/
function createCanvas(_id, _w, _h, _def) { // {{{
var default_content =
'
Ooops. Your browser does not support the HTML 5 Canvas element.
';
// create the element.
var canvas = document.createElement("canvas");
// set the attributes for id, width, and height
canvas.setAttribute("id", _id);
canvas.setAttribute("width", _w);
canvas.setAttribute("height", _h);
// also add the width and height style attributes.
canvas.style.width = _w + "px";
canvas.style.height = _h + "px";
canvas.innerHTML = check(_def, default_content);
return canvas;
}// }}} createCanvas
/**
* A utility function which checks if the given _value exists. If it does, it
* returns that, otherwise it returns the given _default.
* @param _value The value to check for.
* @param _default The value to return if the given _value doesn't exist.
*/
function check(_value, _default) {
if(exists(_value)) {
return _value;
}
return _default;
}
/**
* A convenience function which creates a new Font object using nameless parameters.
* @function
* @param family
* @param size
* @param style
* @param varient
* @param weight
*/
function font(family, size, style, varient, weight) {
var obj = {family: family, size: size, style: style, varient: varient, weight: weight};
return Font(obj);
}
/**
* Utility function to convert degrees into radians.
* @function
* @param {Number} _degrees The degrees to convert.
*/
function toRadians(_degrees) {
return _degrees * Math.PI / 180.0;
}
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class This Content simply prints out the current frames per second.
* @extends Text
* @param {Object} _details A JSON object. This content has the same properties as the Text
* object.
*
*
Property
Required
Default
*
text
no
"FPS: "
*
* "text" is prepended to the actual FPS value: "[text][FPS]"
* @see Text for other properties, and styling information.
* @example // Creates a new FPS object
* FPS({ x: 10, y: 16 })
*
*
* @requires Firefox 3.5.x, Safari, or Google Chrome
*/
function FPS(_details) { // {{{
function _FPS(_details) { // {{{
// Private Members {{{
var text = check(_details.text, "FPS: ");
var updates = 0;
var that = this;
// }}} Private Members
// Public Members {{{
this.update = function(_run_time) {
this.setText(text + (updates++ / (_run_time / Units.seconds)));
if(exists(_details.update)) {
_details.update.apply(this, arguments);
}
}
// }}} Public Members
} // }}} _FPS
Text(_details).extend(_FPS);
var theFps = new _FPS(_details);
return theFps;
} // }}} FPS
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Draws an image into the Scene. The name "Graphic" was chosen over "Image" due
* to the native Javascript Image object. It had to be named something else, to
* avoid browser confusion as to which Image object we're actually creating.
* To create a Graphic, you need to supply it's location and url.
* @example // Using a remote image.
* Graphic({
* x: 10, y: 40,
* url: "http://www.tellurianring.com/images/logo-wordmark-version_100.png"
* })
*
*
* @example // Relative URL's work as well:
* Graphic({
* x: 10, y: 10,
* url: "./images/ball.png"
* })
*
*
* @example // Scaling the image.
* Graphic({
* x: 10, y: 50,
* scale_w: 100, scale_h: 26,
* url: "http://www.tellurianring.com/images/logo-wordmark-version.png"
* })
*
*
* @extends SceneContent
* @param {Object} _details A JSON object with the following properties:
*
*
Property
Required
Default
*
scale_h
no
*
scale_w
no
*
url
yes
*
*/
function Graphic(_details) { // {{{
/** @constructs */
function _Graphic(_details) { // {{{
// Private Members {{{
var image = new Image();
var loaded = false;
var scale_h = _details.scale_h;
var scale_w = _details.scale_w;
var url = _details.url;
// tries to preload the image at initialization.
image.loader = this;
image.onload = function() {
this.loader.doLoad();
}
// }}}
// Public Members {{{
this.doLoad = function() {
loaded = true;
if(exists(scale_w, scale_h)) {
this.setSize({ w: scale_w, h: scale_h });
} else {
this.setSize({w: image.width, h: image.height});
}
if(exists(_details.onLoad)) {
_details.onLoad();
}
}
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
if(loaded) {
if(exists(scale_w, scale_h)) {
_context.drawImage(image, bounds.x, bounds.y, scale_w, scale_h);
} else {
_context.drawImage(image, bounds.x, bounds.y);
}
}
} // }}} draw
// Overriden so the image is actually loaded during the load event from
// the scene.
this.load = function(_scene, _parent) {
this.loadBase(_scene, _parent);
image.src = url;
}
// }}} Public Members
}// }}} _Graphic
SceneContent(_details).extend(_Graphic);
var theGraphic = new _Graphic(_details);
return theGraphic;
} // }}} Graphic
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class LoadingScene Loads a given set of assets. Assets include Graphics and Sounds.
* @example // Displays a single message as assets are loading.
* LoadingScene({
* steps: {
* "Assets": [
* Graphic({ url: "./images/ball.png" }),
* Sound({ url: "./sounds/bang.ogg" }),
* ...
* ]
* }
* })
* // This example will show the following message:
* // "Loading - Assets [percent]% complete, overall [percent]% complete"
* @example // Having two steps for images and sounds.
* LoadingScene({
* steps: {
* "Graphics": [
* Graphic({ url: "./images/ball.png" }),
* ...
* ],
* "Sounds": [
* Sound({ url: "./sounds/bang.ogg" }),
* ...
* ]
* }
* })
* @example // You can also supply your own content.
* // Say you want to display your own background image as the assets are loading
* LoadingScene({
* steps: {
* "Assets": [
* Graphic({ url: "./images/ball.png" }),
* Sound({ url: "./sounds/bang.ogg" }),
* ...
* ]
* },
* content: [
* Graphic({ url: "./images/loading_bg.png" })
* ]
* })
* @param {Object} _details A JSON object with the following properties.
*
*
Property
Required
Default
*
content
no
*
steps
no
*
*/
function LoadingScene(_details) { // {{{
function _LoadingScene(_details) { // {{{
// Private Members {{{
var content = _details.content;
var current_step = 0;
var finished = false;
var step_loaded = 0;
var step_loading = 0;
var step_names = new Array();
var step_totals = {};
var steps = check(_details.steps, {});
var that = this;
var total = 0;
var total_loaded = 0;
var total_loading = 0;
// }}} Private Members
// Public Members {{{
/**
* Creates the default content for this LoadingScene. The default content
* is a Text object which displays the following message in the middle of
* the scene:
* "Loading - [step] [percent]% complete, overall [percent]% complete"
* @function
*/
this.createDefaultContent = function() { // {{{
var scene_bounds = this.getScene().getBounds();
var defaultContent = [
Text({
x: scene_bounds.w / 2, y: scene_bounds.h / 2,
align: Text.Align.center, font: Font({ size: "16px", weight: Font.Weights.bold }),
update: function(_run_time, _step, _step_percent, _total_percent, _finished) {
var text = "Loading - " + _step + " " + Math.round(_step_percent * 100) + "% complete, overall " + Math.round(_total_percent * 100) + "% complete";
if(_finished) {
text = "Loaded!";
}
this.setText(text);
}
})
];
return defaultContent;
} // }}} createDefaultContent
this.draw = function(_context) { // {{{
for(var ci in content) {
var component = content[ci];
component.draw(_context);
}
} // }}} draw
/**
* @function called when all the assets have been loaded. Calls finish
* function of details if one is provided. This function can be used to
* set the scene on the stage after the assets have been loaded.
* @example
* var stage = Stage({
* scene: Scene({
* content: [
* LoadingScene({
* steps: { ... },
* finished: function() {
* stage.unload(); stage.setScene(nextScene); stage.load();
* }
* })
* ]
* })
* });
*/
this.finish = function() { // {{{
finished = true;
if(exists(this.details.finish)) {
details.finish();
}
} // }}} finish
/**
* Loads any content provided then starts the loader thread to preload
* any assets provided in the steps.
* @function
*/
this.load = function(_scene, _parent) { // {{{
this.loadBase(_scene, _parent);
if(!exists(content)) {
content = this.createDefaultContent();
}
// Load content.
for(var ci in content) {
var component = content[ci];
component.load(_scene, that);
}
// set up the info.
var stepCount = 0;
for(var si in steps) {
var step = steps[si];
step_names[stepCount] = si;
var assetCount = 0;
for(var ai in step) {
var asset = step[ai];
assetCount++;
total++;
}
step_totals[si] = assetCount;
stepCount++;
}
// start the loading thread
setTimeout(this.loadNextAsset, 30);
} // }}} load
/**
* Loads the next asset in the current step.
* @function
*/
this.loadNextAsset = function() { // {{{
var step_name = step_names[current_step];
var step = steps[step_name];
// get next current asset and increment loading variables.
var asset = step[step_loading++]; total_loading++;
asset.details.onload = function() {
// increment loaded variables.
total_loaded++; step_loaded++;
if(step_loading < step_totals[step_name]) {
// if loading is less than total number of assets for current step
// we load the next one.
setTimeout(that.loadNextAsset, 30);
} else if (++current_step < step_names.length) {
// else if the next current_step is less than total number of steps
// start on the next step.
step_loaded = 0;
step_loading = 0;
setTimeout(that.loadNextAsset, 30);
} else {
// we're done.
that.finish();
}
};
asset.load(that.getScene(), that);
} // }}} loadNextAsset
/**
* Updates all content within this LoadingScene. This update function
* differs from the Scene object because it passes extra information to
* each content. It calls each component's update function with the
* following:
*
*
Property
Type
Description
*
runtime
Integer
Current time in milliseconds since the scene was loaded.
*
step
String
Current step name as defined in the _details object.
*
step_percent
Decimal
Percent complete within the current step.
*
total_percent
Decimal
Percent complete overall.
*
finished
Boolean
Have all the assets been loaded.
*
* @function
*/
this.update = function(_run_time) { // {{{
var current_step_name = step_names[current_step];
var step_total = step_totals[current_step_name];
for(var ci in content) {
var component = content[ci];
component.update(_run_time, current_step_name, step_loaded / step_total, total_loaded / total, finished);
}
} // }}} update
// }}} Public Members
} // }}} _LoadingScene
SceneContent(_details).extend(_LoadingScene);
var theLoadingScene = new _LoadingScene(_details);
return theLoadingScene;
} // }}} LoadingScene
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class A Path builder which can be used to draw pretty much any shape. Due to
* current Canvas path restrictions, a Path's width and height are always equal
* to the width and height passed into the Path factory function. This is unlike
* other Groups in where the Groups bounds is calculated based on it's sub content.
* @extends Group
* @example // A Paths bounds is equal to the bounds passed in.
* var path = Path({ x: 10, y: 10, w: 25, h: 30 });
* alert(path.getBounds().x == 10 && path.getBounds().w == 25); // alerts true
* path.moveBy({ x: 10 });
* alert(path.getBounds().x == 20); // alerts true
*
*
* @example
* // Creating a circle.
* Path({ fill: "#CCCCCC" }).jumpTo(75, 50)
* .arc(50, 50, 25, 0, 360 * (Math.PI / 180), true)
*
*
* @example
* // Creating a box.
* Path({x: 10, y: 10, stroke: "#333333"})
* .lineTo(50, 50)
* .lineTo(50, 90)
* .lineTo(10, 90)
* .lineTo(10, 10)
*
*
* @example
* // Splitting the builder across multiple lines.
* var path = Path({x: 10, y: 10, stroke: "#333333"});
* path.lineTo(50, 50);
* path.lineTo(50, 90);
* path.lineTo(10, 90);
* path.lineTo(10, 10);
*
*
* @extends Group
* @param {Object} _details A JSON Object with these properties.
*
*
Property
Required
Default
*
fill
no
transparent
*
stroke
no
#000000
*
stroke_width
no
1
*
*/
function Path(_details) { // {{{
/** @constructs */
function _Path(_details) { // {{{
// Private members {{{
var fill = check(_details.fill, "transparent");
var stroke = check(_details.stroke, "#000000");
var stroke_width = check(_details.stroke_width, 1);
// }}}
// Public Members {{{
/**
* Dreates an arc (based on a circle) in this path.
* @function
* @param _x The center x coord. of the circle.
* @param _y The center y coord. of the circle.
* @param _start The starting angle of this arc in radians.
* @param _end The ending angle of this arc in radians.
* @param _clockwise Should this arc go clockwise.
*/
this.arc = function(_x, _y, _radius, _start, _end, _clockwise) {
this.getContent().push(Arc({x: _x, y: _y, radius: _radius, start: _start, end: _end, clockwise: _clockwise}));
return this;
}
/**
* Draws a bezier curve from the current position of the path to the
* given coordinates.
* @function
* @param _cpx1 The control point x1.
* @param _cpy1 The control point y1.
* @param _cpx2 The control point x2.
* @param _cpy2 The control point y2.
* @param _x The x coord. to draw to.
* @param _y The y coord. to draw to.
*/
this.beziTo = function(_cpx1, _cpy1, _cpx2, _cpy2, _x, _y) { // {{{
this.getContent().push(BezierTo({cpx1: _cpx1, cpy1: _cpy1, cpx2: _cpx2, cpy2: _cpy2, x: _x, y: _y}));
return this;
} // }}} lineTo
// Overridden to draw this path.
this.draw = function(_context) {
var bounds = this.getBounds();
var content = this.getContent();
_context.save();
_context.beginPath();
_context.moveTo(bounds.x, bounds.y);
for(var index in content) {
var component = content[index];
component.draw(_context);
}
if(exists(fill)) {
_context.fillStyle = fill;
_context.fill();
}
if(exists(stroke)) {
_context.strokeStyle = stroke;
_context.lineWidth = stroke_width;
_context.stroke();
}
_context.closePath();
_context.restore();
}
/**
* Moves the next part of this path to the given coordinates.
* This function does not draw anything.
* @function
* @param _x The x coord. to jump to.
* @param _y The y coord. to jump to.
*/
this.jumpTo = function(_x, _y) { // {{{
this.getContent().push(JumpTo({x: _x, y: _y}));
return this;
} // }}} jumpTo
/**
* Draws a line from the current position of the path to the given coordinates.
* @function
* @param _x The x coord. to draw to.
* @param _y The y coord. to draw to.
*/
this.lineTo = function(_x, _y) { // {{{
this.getContent().push(LineTo({x: _x, y: _y}));
return this;
} // }}} lineTo
/**
* Draws a quadratic curve from the current position of the path to the
* given coordinates.
* @function
* @param _cpx The control point x.
* @param _cpy The control point x.
* @param _x The x coord. to draw to.
* @param _y The y coord. to draw to.
*/
this.quadTo = function(_cpx, _cpy, _x, _y) { // {{{
this.getContent().push(QuadraticTo({cpx: _cpx, cpy: _cpy, x: _x, y: _y}));
return this;
} // }}} quadTo
// Overriden because Group's update method calculates bounds based on
// sub content.
this.update = function(_runtime) {
this.updateBase(_runtime);
}
// }}}
} // }}} _Path
/**
* Draws an arc from the current location to a new one.
* @private
*/
function Arc(_details) { // {{{
/** @constructs */
function _Arc(_details) { // {{{
// Private Members {{{
var radius = check(_details.radius, 0);
var start = check(_details.start, 0);
var end = check(_details.end, 0);
var clockwise = check(_details.clockwise, true);
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
// arc function asks for anti-clockwise parameters. So we just
// negate the clockwise value.
_context.arc(bounds.x, bounds.y, radius, start, end, !clockwise);
} // }}} draw
// }}} Public Members
} // }}} _Arc
SceneContent(_details).extend(_Arc);
var theArc = new _Arc(_details);
return theArc;
} // }}} Arc
/**
* Draws a Bezier curve from the current location to a new one.
* @private
*/
function BezierTo(_details) { // {{{
/** @constructs */
function _BezierTo(_details) { // {{{
// Private Members {{{
var cpx1 = check(_details.cpx1, 0);
var cpy1 = check(_details.cpy1, 0);
var cpx2 = check(_details.cpx2, 0);
var cpy2 = check(_details.cpy2, 0);
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
_context.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, bounds.x, bounds.y);
} // }}} draw
// }}} Public Members
} // }}} _BezierTo
SceneContent(_details).extend(_BezierTo);
var theBezierTo = new _BezierTo(_details);
return theBezierTo;
} // }}} BezierTo
/**
* Moves the context to a new location.
* @private
*/
function JumpTo(_details) { // {{{
/** @constructs */
function _JumpTo(_details) { // {{{
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
_context.moveTo(bounds.x, bounds.y);
} // }}} draw
// }}} Public Members
} // }}} _JumpTo
SceneContent(_details).extend(_JumpTo);
var theJumpTo = new _JumpTo(_details);
return theJumpTo;
} // }}} JumpTo
/**
* Draws a line from the current position to a new one.
* @private
*/
function LineTo(_details) { // {{{
/** @constructs */
function _LineTo(_details) { // {{{
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
_context.lineTo(bounds.x, bounds.y);
} // }}} draw
// }}} Public Members
} // }}} _LineTo
SceneContent(_details).extend(_LineTo);
var theLineTo = new _LineTo(_details);
return theLineTo;
} // }}} LineTo
/**
* Draws a Quadratic curve from the current location to a new one.
* @private
*/
function QuadraticTo(_details) { // {{{
/** @constructs */
function _QuadraticTo(_details) { // {{{
// Private Members {{{
var cpx = check(_details.cpx, 0);
var cpy = check(_details.cpy, 0);
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
_context.quadraticCurveTo(cpx, cpy, bounds.x, bounds.y);
} // }}} draw
// }}} Public Members
} // }}} _QuadTo
SceneContent(_details).extend(_QuadraticTo);
var theQuadTo = new _QuadraticTo(_details);
return theQuadTo;
} // }}} QuadTo
Group(_details).extend(_Path);
var thePath = new _Path(_details);
return thePath;
} // }}} Path
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Draws a rectangle into the Scene. To create a Rectangle, you need only supply
* it's location and size, along with at least 1 of 3 rendering instructions.
* The 3 rendering instructions are "fill", "stroke", and "clear".
* The fill rendering instruction tells this rectangle to fill the rectangle
* with the given color.
* The stroke rendering instruction tells this rectangle to stroke (outline) the
* rectangle with the given color. You can also specify a stroke_width, which
* is used to define the line thickness used while outlining the rectangle.
* The clear rendering instruction actually is set to "true" or "false", and it
* tells the rectangle to clear/remove the area behind it.
*
* @example
* To draw a red rectangle with a blue outline, you might define it as such:
* Rectangle({
* x: 25, y: 25, w: 50, h: 50,
* fill: "red", stroke: "blue"
* })
* This creates a red square at (0,0) with width and height of 50, and a blue
* outline.
*
*
* @extends SceneContent
* @param {Object} _details A JSON Object with the following properties:
*
*
Property
Required
Default
*
clear
no
false
*
fill
no
transparent
*
stroke
no
#000000
*
stroke_width
no
1
*
*/
function Rectangle(_details) { // {{{
/** @constructs */
function _Rectangle(_details) { // {{{
// Private Members {{{
var clear = check(_details.clear, false);
var fill = check(_details.fill, "transparent");
var stroke = check(_details.stroke, "#000000");
var stroke_width = check(_details.stroke_width, 1);
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
if(exists(fill)) {
_context.fillStyle = fill;
_context.fillRect(bounds.x, bounds.y, bounds.w, bounds.h);
}
if(exists(stroke)) {
_context.strokeStyle = stroke;
if(typeof stroke_width != 'undefined') {
_context.lineWidth = stroke_width;
}
_context.strokeRect(bounds.x, bounds.y, bounds.w, bounds.h);
}
if(clear) {
_context.clearRect(bounds.x, bounds.y, bounds.w, bounds.h);
}
} // }}} draw
// }}} Public Members
} // }}} _Rectangle
SceneContent(_details).extend(_Rectangle);
var theRectangle = new _Rectangle(_details);
return theRectangle;
} // }}}
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Loads some audio or sound into the Scene. This implementation uses the
* HTML 5 audio tag and therefore requires a compatible browser.
* @example
* var sound1 = Audio({
* url: "http://www.tellurianring.com/sounds/laugh.ogg"
* });
* sound1.play();
*
*
*
* @example
* Relative URL's work as well:
* var sound2 = Audio({ url: "./sounds/ohhh.ogg" });
* sound2.play();
*
*
*
* @extends SceneContent
* @param {Object} _details A JSON object with the following properties:
*
*
Property
Required
Default
*
loop
no
false
*
start
no
false
*
url
yes
*
volume
no
1
*
*/
function Sound(_details) { // {{{
/** @constructs */
function _Sound(_details) { // {{{
// Private Members {{{
var audio = document.createElement("audio");
var loaded = false;
var loop = check(_details.loop, false);
var start = check(_details.start, false);
var url = _details.url;
var volume = check(_details.volume, 1);
// tries to preload the image at initialization.
audio.autoplay = start;
audio.loader = this;
audio.loop = loop;
audio.onload = function() {
this.loader.doLoad();
};
audio.volume = volume;
// }}} Private Members
// Public Members {{{
/**
* Executed when the audio is actually loaded.
*/
this.doLoad = function() {
loaded = true;
if(exists(_details.onLoad)) {
_details.onLoad();
}
}
// Overridden to load audio at load time instead of creation time.
this.load = function(_scene, _parent) {
this.loadBase(_scene, _parent);
audio.src = url;
audio.load()
}
/**
* Sets the volume to the initial volume, and starts the sound.
* @function
*/
this.play = function() {
audio.play();
}
/**
* Pauses the sound.
* @function
*/
this.pause = function() {
audio.pause();
}
/**
* Sets the looping of this Sound.
* @function
* @param {Boolean} _loop Turns looping on for this sound - true or false.
*/
this.setLoop = function(_loop) {
loop = _loop;
audo.loop = _loop;
}
/**
* Sets the volume on this Sound.
* @function
* @param {Decimal} _volume Volume of sound between 0.0 and 1.0
*/
this.setVolume = function(_volume) {
volume = _volume;
audo.volume = _volume;
}
/**
* Stops the sound. This implementation pauses the sound and resets it.
*/
this.stop = function() {
this.pause();
audio.currentTime = 0;
}
// Overridden to react to stage unloads - which cause the sound to stop.
this.update = function(_runtime) {
if(this.getStage().isRunning() == false) {
this.pause();
}
}
// }}} Public Members
}// }}} _Sound
SceneContent(_details).extend(_Sound);
var theSound = new _Sound(_details);
return theSound;
} // }}} Sound
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Text Draws some text on the Stage. This implementation uses the WHATWG
* specifications and is only available in certain browsers.
* @extends SceneContent
* @see WHATWG Canvas and Text
* @param {Object} _details An JSON object containing the following properties:
*
*
Property
Required
Default
*
align
no
start
*
baseline
no
alphabetic
*
fill
no
#000000
*
font
no
Font({family: ["Tahoma", "Ariel"]})
*
stroke
no
transparent
*
stroke_width
no
1
*
text
no
""
*
* @example // Simple example
* Text({ x: 10, y: 50, text: "HTML 5 Rocks!" })
*
*
* @example // Centering text.
* Text({ x: 50, y: 50, text: "HTML 5 Rocks!", align: Text.Align.center })
*
*
* @example // Making it cooler.
* Text({
* x: 50, y: 50, text: "~CooL~", align: Text.Align.center,
* fill: "#DFDFFF", stroke: "#413FFF", stroke_width: 2,
* font: Font({ size: "18px", weight: Font.Weights.bold })
* })
*
*
* @requires Firefox 3.5.x, Safari, or Google Chrome
*/
function Text(_details) { // {{{
/** @private @constructs */
function _Text(_details) { // {{{
// Private Members {{{
var align = check(_details.align, Text.Align.start);
var baseline = check(_details.baseline, Text.Baseline.alpha);
var fill = check(_details.fill, "#000000");
var font = check(_details.font, Font({family: ["Tahoma", "Ariel"]}));
var stroke = check(_details.stroke, "transparent");
var stroke_width = check(_details.stroke_width, 1);
var text = check(_details.text, "");
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
var bounds = this.getBounds();
var x = bounds.x;
var y = bounds.y;
_context.textAlign = align;
_context.textBaseline = baseline;
_context.font = font.toCss();
if(exists(fill) && exists(_context.fillText)) {
_context.fillStyle = fill;
_context.fillText(text, x, y)
}
if(exists(stroke) && exists(_context.strokeText)) {
_context.lineWidth = stroke_width;
_context.strokeStyle = stroke;
_context.strokeText(text, x, y);
}
} // }}} draw
/**
* Sets the text of this Text object.
* @function
* @param {String} _text The text to display.
*/
this.setText = function(_text) { // {{{
text = _text;
}// }}} setText
// }}} Public Members
} // }}} _Text
SceneContent(_details).extend(_Text);
var theText = new _Text(_details);
return theText;
} // }}} Text
/**
* @namespace Some Text Alignment Constants.
*/
Text.Align = {
/** @property */
center: "center",
/** @property */
end: "end",
/** @property */
left: "left",
/** @property */
right: "right",
/** @property */
start: "start"
};
/**
* @namespace Some Text Baseline Constants.
*/
Text.Baseline = {
/** @property */
alpha: "alphabetic",
/** @property */
alphabetic: "alphabetic",
/** @property */
bot: "bottom",
/** @property */
bottom: "bottom",
/** @property */
hang: "hanging",
/** @property */
hanging: "hanging",
/** @property */
ideo: "ideographic",
/** @property */
ideographic: "ideographic",
/** @property */
mid: "middle",
/** @property */
middle: "middle",
/** @property */
top: "top"
};
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
/**
* @class Rotate's a group around a given pivot point.
* @extends Group
* @param {Object} _details A JSON Object describing the rotation of a group
* of content. The object may have these properties:
*
*
Property
Required
Default
*
angle
no
0 (Radians)
*
pivot
no
The middle of this group, based on cumulative bounds.
*
* @example // Simple Example - rotating an image 45 deg.
* Rotate({ x: 25, y: 20.5, angle: toRadians(45),
* content: [
* Graphic({ url: "./images/beetleship.png" })
* ]
* })
*
*
*/
function Rotate(_details) { // {{{
/** @constructs */
function _Rotate(_details) { // {{{
// Private Members {{{
var angle = check(_details.angle, 0);
var pivot = _details.pivot;
// }}} Private Members
// Public Members {{{
this.draw = function(_context) { // {{{
// draw the groups contents to a different canvas.
var bounds = this.getBounds();
var content = this.getContent();
_context.save();
// translate the context to the center of where the group would be
// drawn.
var pivot_point = {x: (bounds.w / 2), y: (bounds.h / 2)};
if(exists(pivot)) {
pivot_point = pivot;
}
_context.translate(bounds.x + pivot_point.x, bounds.y + pivot_point.y);
// then rotate it.
_context.rotate(angle);
// then translate it back to where the group will begin to be drawn.
_context.translate( -pivot_point.x, -pivot_point.y);
for(var index in content) {
var component = content[index];
component.draw(_context);
}
_context.restore();
} // }}} draw
/**
* Sets this rotation's angle to the given amount.
* @param _angle The angle this Rotation group starts at.
*/
this.rotate = function(_angle) { // {{{
angle = _angle;
} // }}} rotate
/**
* Rotates this group by the given amount. This method simply adds the
* given delta angle to the starting angle.
* @param _dangle The amount to rotate this group by.
*/
this.rotateBy = function(_dangle) { // {{{
angle += _dangle;
} // }}} rotateBy
// }}} public Members
} // }}} _Rotate
Group(_details).extend(_Rotate);
var theRotation = new _Rotate(_details);
return theRotation;
} // }}} Rotate
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=true:collapseFolds=4::
// These properties are for jEdit - Programmer's Text Editor.
// Load this file in jEdit to see what they do.
// ::folding=explicit:mode=javascript:noTabs=false:collapseFolds=4::