Currently, this is written as if the user has experience with other scene graphs. Will include more information later.

• TODO: Syntax Highlighter seems to be having issues with JS single quotes. Find other solution or submit pull request for fix

Hello World

Include the Scenery script in the page after jQuery and LoDash, and create a block-level element (like a div) that has a width and height that won't change with its contents:

<div id="hello-world-scene" style="width: 400px; height: 100px;"></div>


The following JS code will initialize a scene graph, add some centered text into the scene, and paint it into the div.

// Create a Node as the root of our tree (it can contain children)
var scene = new scenery.Node();

// Create a display using this root node, using our existing div
var display = new scenery.Display( scene, {
container: document.getElementById( 'hello-world-scene' )
} );

scene.addChild( new scenery.Text( 'Hello World', {
centerX: 200, // the center of our text's bounds is at x = 200
centerY: 50, // the center of our text's bounds is at y = 50
font: '25px sans-serif' // any CSS 'font' declaration will work
} ) );

// Paint any changes (in this case, our text).
display.updateDisplay();


There is a standalone example of this Hello World demo.

Shapes

Nodes are the basic structure of the scene graph, and can be given shapes. Shapes can be built from convenience functions, or from Canvas-style calls. Exact bounding boxes of shapes are calculated, including any strokes.

// A rectangle at (10,10) with a width of 50 and height of 80
scene.addChild( new scenery.Path( kite.Shape.rectangle( 10, 10, 50, 80 ),{
fill: '#ff0000'
} ) );

// An octogon, with a radius of 30
scene.addChild( new scenery.Path( kite.Shape.regularPolygon( 8, 30 ),{
fill: '#00ff00',
stroke: '#000000',
x: 100, // the shape is centered at the origin, so offset it by this x and y
y: 50
} ) );

// Custom shapes can also be created
var customShape = new kite.Shape();
customShape.moveTo( 0, 0 );
customShape.lineTo( 40, 40 );
customShape.quadraticCurveTo( 80, 0, 40, -40 );
customShape.lineTo( 40, 0 );
customShape.close();
// NOTE: this can also be chained, like shape.moveTo( ... ).lineTo( ... ).close()

fill: '#0000ff',
stroke: '#000000',
lineWidth: 3,
x: 150,
y: 50
} ) );


Nodes

Nodes can be created and modified currently in a few different ways:

// The following are all equivalent ways to create a node, listed in approximate order of increasing performance (if it is relevant).

var someShape = ...; // a shape used for brevity in the following calls, otherwise setting the bottom of the node makes no sense

// Declarative pattern. NOTE: the parameters are executed in a specific order which is particularly important
// for transformation and bounds-based parameters. Using translations like 'x' or 'bottom' do not change the
// point around which rotation or scaling is done in the local coordinate frame, and bounds-based modifiers
// like 'bottom' (which moves the bottom of the node's bounds to the specific y-value) are handled after other
// modifiers have been run.
var node1 = new scenery.Path( someShape, {
fill: '#000000',
rotation: Math.PI / 2,
x: 10,
bottom: 200
} );

// ES5-setter style, called internally by the declarative paramter object style above
var node2 = new scenery.Path();
node2.shape = someShape;
node2.fill = '#000000';
node2.x = 10; // note the reordering, as this is precisely how the declarative style is executed
node2.rotation = Math.PI / 2;
node2.bottom = 200;

// Java-style setters, called internally from the ES5-setter style above
var node3 = new scenery.Path();
node3.setShape( someShape );
node3.setFill( '#000000' );
node3.setX( 10 );
node3.setRotation( Math.PI / 2 );
node3.setBottom( 200 );

// Chained style, since setters return a this-reference.
var node4 = new scenery.Path().setShape( someShape ).setFill( '#000000' ).setX( 10 ).setRotation( Math.PI / 2 ).setBottom( 200 );

// Additionally, parameters can be accessed by two styles:
node2.rotation === node3.getRotation();


Similarly to Piccolo2d, nodes have their 'self' paint/bounds and the paint/bounds of their children, where the node's self is rendered first (below), and then children are painted one-by-one on top. Additionally, it borrows Piccolo's coordinate frames where there are local, parent, and global coordinate frames. The example below will hopefully be illustrative.

// create a node that will have child nodes.
var container = new scenery.Path( kite.Shape.rectangle( 0, 0, 300, 100 ), {
// we'll add a background shape (300x100 rectangle) that will render on this node (it will be rendered below any child nodes)
fill: '#888888',
x: 20, // this node and all of its children will be offset by (20,20)
y: 20
} );

// bounding box of the container (and its children so far) in its parent's coordinate system.
// translated by (20,20 due to the x,y parameters)
container.getBounds(); // x between [20, 320], y between [20, 120]

// bounding box of the container's own rendering/paint (the rectangle) in its local coordinate frame (without applying other transforms)
container.getSelfBounds(); // x between [0, 300], y between [0, 100]

// add a green rectangle to the container, which will be above the gray background
var child1 = new scenery.Path( kite.Shape.rectangle( 0, 0, 25, 25 ), { // a 25x25 rectangle
fill: '#00ff00',
scale: 2, // but scaled so it is effectively a 50x50 rectangle to its parents
left: 10, // 10px to the right of our parent's left bound
centerY: container.height // vertically center us on the container's bottom bound
} );

// bounding box of the child in its parent's coordinate frame (the container's local coordinate frame)
// note that since it is scaled 2x, its dimensions appear to be 50x50
child1.getBounds(); // x between [10, 60], y between [75, 125], (with a left of 10 and centered vertically on 100)

child1.getSelfBounds(); // x between [0, 25], y between [0, 25] -- just bounds of its shape

// but now that we have added a child, getBounds() on the container (which contains the bounds of its children) has changed:
container.getBounds(); // x between [20, 320], y between [20, 145] -- bottom bound of 145 since it has an x offset of 20 plus its child's bottom of 125

// and some text to the same container, which will be on top of both the background and green rectangle
var child2 = new scenery.Text( 'On Top?', {
left: child1.centerX,
centerY: child1.centerY,
font: '20px sans-serif'
} );


Images

For now, pass in a valid image and it will be rendered with its upper-left corner at 0,0 in the local coordinate frame.

// TODO: support different ways of handling the asynchronous load
var thumbnailImage = document.createElement( 'img' );
thumbnailImage.onload = function( e ) {
scene.addChild( new scenery.Image( thumbnailImage ) );
x: 200,
y: 25,
scale: 1.5,
rotation: Math.PI / 4
} ) );
display.updateDisplay();
};


DOM Elements

DOM elements can be added in (they are transformed with CSS transforms). Currently, bounds-based modifiers may be buggy.

var element = document.createElement( 'form' );
element.innerHTML = 'Type in me:';

x: 50,
rotation: Math.PI / 4
} ) );


When added in the above manner, the DOM element will be lifted in front of any other non-lifted elements by default. It is possible to have the DOM elements reside where they would normally be rendered.

Animation

It is recommended to use display.updateDisplay() inside of requestAnimationFrame(), since updateScene() attempts to only re-paint content that is inside of changed areas. For simple usage, you can use updateOnRequestAnimationFrame() on the display.

var scene = new scenery.Node();
var display = new scenery.Display( scene, {
container: document.getElementById( 'animation-simple' )
} );

// a hexagon to rotate
var node = new scenery.Path( kite.Shape.regularPolygon( 6, 90 ), {
fill: '#000000',
centerX: 100,
centerY: 100
} );
// a marker so pauses on the iPad animation (since requestAnimationFrame doesn't trigger while scrolling) are visible
node.addChild( new scenery.Path( kite.Shape.rectangle( 0, -3, 60, 6 ), {
fill: '#ff0000'
} ) );

// given time elapsed in seconds
display.updateOnRequestAnimationFrame( function( timeElapsed ) {
node.rotate( timeElapsed );
} );


Input Events

The input event system is somewhat different from many other scene graphs, since it hopes to accomodate multi-touch interaction with the mouse. It comes with a low-level event system of which then gestures and behavior can be built on top. For instance, what if in some cases you want a zoom-pinch to be interrupted by another pointer manipulating a control? When are pointers interacting with elements individually (sliders, dragging objects, etc.), when are they acting together (zooming, rotating, etc.), and how does this behavior change?

An instance of scenery.Input is created and hooked to an event source with functions like Scene.initializeEvents(). The Input object tracks the principal abstraction of a pointer. A pointer represents either the mouse or a single touch as it is tracked across the screen. The mouse 'pointer' always exists, but touch pointers are transient, created when an actual pointer is pressed on a touchscreen and detached when it is lifted (or canceled). Input event listeners can be added to nodes, the scene, or actual pointers themselves. Attaching a listener to a pointer allows tracking of that pointer's state, and when combined with behavioral flags can create advanced input handling systems. Also to note: preventDefault (triggered on events by default) will prevent touch events from being re-fired as mouse events, so we only see them once.

Events can be handled by any input listener (return true from the callback) and it will not fire any subsequent input listeners for that event. For a single touchmove event, individual pointers generate their own events, so each input event will have a single associated pointer. The order of listeners visited for each event is as follows:

1. Listeners attached to the associated pointer
2. For move/up/down/cancel events, listeners for the node directly beneath the event, and subsequently for all parent (ancestor) nodes in order up to the root node
3. For enter/exit events, listeners similar to move/up/down/cancel events, but only up to (and not including) the common parent before/after the action. TODO: better explanation
4. Listeners attached to the scene

DOM interaction with the event system is still being worked on. Gesture listeners and full pointer-list access will be added.

Below are a series of examples that will hopefully show off the current system.

Colored by quantity of pointers inside shape

var scene = new scenery.Node();
var display = new scenery.Display( scene, {
container: document.getElementById( 'input-mouseover' )
} );

// hook up event listeners just on this scene (not the whole document)
display.initializeEvents();

var count = 0;
var colors = [ '000000', 'ff0000', '00ff00', '0000ff', 'ffff00', '00ffff', 'ff00ff', 'ffffff' ];
var maxColor = colors.length - 1;

var labelPrefix = 'Pointers in hexagon: ';
var label = new scenery.Text( labelPrefix + count, {
left: 20,
top: 20,
font: '20px sans-serif'
} );

// a big hexagon in the center. stroke not included in the hit region by default
var node = new scenery.Path( kite.Shape.regularPolygon( 6, 150 ), {
fill: colors[count],
stroke: '#000000',
centerX: 200,
centerY: 200
} );

// update our label and color
function updatePointers() {
node.fill = colors[ Math.min( count, maxColor ) ];
label.setText( labelPrefix + count );
}

// listener fired whenever the event occurs over the node
// below are the main 6 input events that are fired
// mousedown or touchstart (pointer pressed down over the node)
down: function( event ) {
if ( !( event.pointer instanceof scenery.Mouse ) ) {
count++;
updatePointers();
}
},

// mouseup or touchend (pointer lifted from over the node)
up: function( event ) {
if ( !( event.pointer instanceof scenery.Mouse ) ) {
count--;
updatePointers();
}
},

// triggered from mousemove or touchmove (pointer moved over the node from outside)
enter: function( event ) {
count++;
updatePointers();
},

// triggered from mousemove or touchmove (pointer moved outside the node from inside)
exit: function( event ) {
count--;
updatePointers();
},

// platform-specific trigger.
// on iPad Safari, cancel can by triggered by putting 4 pointers down and then dragging with all 4
cancel: function( event ) {
count--;
updatePointers();
},

// mousemove (fired AFTER enter/exit events if applicable)
move: function( event ) {
// do nothing
}
} );

// repaint loop without doing anything extra per-frame
display.updateOnRequestAnimationFrame();


Node Dragging with Swipe-across-to-start-drag

Visit this standalone example, and you can drag multiple hexagons with multiple pointers, and sliding a pointer across a touch-screen (i.e not the mouse) will pick up the first hexagon it slides across, into a drag.

Appendix (to be completed later)

An example of math with MathJax, verifying it doesn't trample dollar signs in pre tags: $f(\theta)^2$, so we'll able to include the necessary discussions using matrix algebra, etc.