A lightweight scene-tree for use with html5's canvas. fork me!
Carena's core feature-set is currently very minimal, providing only a small set methods:
Registers fn with carena's feature loader
This is generally used inside of a feature to make sure that the incoming object has all of the required features.
Example:
carena.addFeature("some.feature", function(obj, options, storage) { // some.feature requires obj, to have carena.Node features carena.require("carena.Node", arguments); });
All properties from the props argument are copied onto the obj, allowing for the copying of enumerable getters/setters.
builds a tree with a common set of features. (Still under r&d)
builds an object to your specifications. obj is your original object that you want to build on top of. features is an array of carena enabled features (for the base list see the Dynamic Features section below). options is simply an object that gets passed to every feature that is used to build your new object.
Use carena.design for building a factory that builds nodes that share the same features. The signature is exactly the same as carena.build, except for the fact that it returns a function that also has the same signature, and behaves exactly like carena.build.
This method finds the common ancestor between two nodes. Example
[a] / \ [b] [c] / \ [d] [e]The common ancestor of d and e in the previous example is c.
Dynamic features are used to augment the features of an existing object.
Utilize the carena.build method to augment objects with new features; this is generally done by calling carena.build(obj, [carena.feature.{feature1}, carena.feature.{feature2}]). This will return you your original obj augmented with feature1 and feature2
Built objects contain a node.dehydrate() method, which makes it really simple to serialize the tree.
Features are functions that take 3 arguments:
The Node is the carena's core feature which allows nodes to positioned, styled, organized into trees, and traversed.
on read: returns the current value
on write: This node and parents are marked dirty. If this node is Eventable, a "node.x" event is triggered:
{ node : node, current : `node.x`, previous : `previous node.x` }with data.previous set to the old x value, and node.current set to the current value.
on read: returns the current value
on write: node and parents are marked dirty. If this node is Eventable, a "node.y" event is triggered:
{ node : node, current : `current node.y`, previous : `original node.y` }
on read: returns the current value
on write: This node and parents are marked dirty. If this node is Eventable, a "node.z" event is triggered:
{ node : node, current : `current node.z`, previous : `original node.z` }
on read: returns the current value
on write: This node and parents are marked dirty. If this node is Eventable, a "node.width" event is triggered:
{ node : node, current : `current node.width`, previous : `original node.width` }
on read: returns the current value
on write: This node and parents are marked dirty. If this node is Eventable, a "node.height" event is triggered:
{ node : node, current : `current node.height`, previous : `original node.height` }
returns the children of node (array)
Dirtyness is a way for node's caches to be re-calculated. For instance, if a node hasn't changed since the last render then there is no reason to re-render the node, as it would simply waste cycles.
on read: returns true or false
on write: a "node.dirty" event will be triggered:
{ node : node, current : `node.parent`, previous : `previous node.parent` }If set to true, and the current node is currently clean, this node and all parent nodes will be marked dirty.
on read: return the current node's parent or null
on write: This node and parents are marked dirty. If this node is Eventable, a "node.parent" event is triggered:
{ node : node, current : `node.parent`, previous : `previous node.parent` }
If the node is dirty, the bounding box of this node will be computed by descending into it's children (recursively) to find the biggest bounding rectangle that fits all of its children.
NOTE: at the time of this writing, re-calculating the bounds also marks the node clean (node.dirty = false).
Adds child to the current node's children array, reparents the child, and triggers "node.child" event:
{ node : node, child : child }returns node or false if a recursive structure is detected.
If child is a child of node then it removes the child and triggers a "node.remove" event:
{ node : node, child : child }returns node
Generic tree walking utility. map resolve nodes that need to be traversed, every new node is passed as a parameter into callback along with a walker object. The walker object tracks the depth and will stop map from going past the level specified (if any).
map(obj, fn) provides a generic means for iterating through object properties. The map function takes two parameters, an object and the callback passed into walk.
callback(obj, walker) called on every node that map encounters. walker is an object that allows you to cancel the entire traversal by calling walker.stop(). Returning false from the callback will cancel the traversal down the current branch.
depth Optional; if specified it forces map to only recurse depth times. If not specified, walk will recurse until all maps on the traversed nodes are exhausted.
Example:
function ascend(obj, fn, depth) { return obj.walk(function(node, callback) { if (node && node.parent) { callback(node.parent); } },fn, depth); };This will traverse up the tree from obj, to the root calling fn every time it encounters a new node.
Uses node.walk to walk up the tree by using node.parent while passing nodes it encounters back to fn.
return walker (see: node.walk)
Uses node.walk to walk down the tree by using node.children while passing nodes it encounters back to fn.
return walker (see: node.walk)
return child at idx, or null
Using node.bounds (if bounding is true) or node.x and node.y to calculate whether a point lies in the area occupied by node.
return true if found or false otherwise
Marks node.dirty false.
return node
Inserts child at the begining of the node.children array.
return node
return the numeric index of the child or -1 if not found.
Uses node.walk to walk down the tree looking for nodes that contain x and y.
return an array of nodes that contain x and y (does not use node.bounds)
The Eventable feature provides a means for objects to communicate via events.
All events that are triggered are handled first by the current node's bound event handlers, and then the event flows upward towards the root of the scene in a "bubbling" fashion. Bubbling is typically achieved by calling trigger on a parent node, if the parent node is Eventable.
An event handler (fn parameter to the node.event.bind event below) that returns false will cause the "bubbling" to stop immediately. No other event handlers will be called.
Many events are namespaced for organization's sake. Examples of namespaced events are "node.x", "node.parent", "drag.start", etc..
All functions exposed by the Eventable feature take a name parameter, which may contain a 'namespace.*' for applying the operation to the entire namespace and any children namespaces (recursively).
Eventable adds the following capabilities to an object:
name is the name of the event that you would like to capture. Event names may contain .*'s for capturing entire namespaces of events at a time (i.e: node.* will capture every node event).
fn is a callback function that takes two arguments: name and data. name is the actual name of the event, and data is an object of important information regarding that event.
NOTE: returning false from the fn callback will cause the current event to stop bubbling.
Use this to unbind events from a node. fn is optional, and not providing it will remove all callbacks associated with the name.
Emits an event on node. data is an that is sent along with the event. If none of the event handlers on node cancel the event (return false), then the same event is triggered on the parent of node (if possible).
return false if the event was handled or cancelled
When binding or triggering events, you may trigger by the entire name or by namespace. For example, If you bound to "node.*" the following events would be caught:
The Renderer feature is used to hold an instance of the rendering context, manage when and how a tree is partially re-rendered, and when to clear the context and start rendering from a blank context.
NOTE: Renderer requires you provide an options.canvas object which implements getContext("2d"), and options.canvas.getContext() returns an object compatible with the html5 canvas element's 2d context.
on read returns the DOM Canvas element
on read returns the raw 2d context object
on read return the current clear color
on write mark the renderer dirty, which will trigger a complete re-draw on the next render pass.
When called the root.walk method is invoked in a way that will climb down into root's children recursively, calling node.render on each one.
Dirty nodes will not be rendered infact, because of the way dirty works,
entire branches of the tree are skipped if their root is marked dirty.
return walker (see: node.walk)
This method clears the context to the
node.clearColor
.
root is then passed into node.renderTreefor rendering of the rest of the target tree.
return node
The job of this feature is to provide a way to view branches of the scene tree using the Renderer feature. The camera is attached to a node by setting its camera.target property. While attached to a target the Camera will forward all events triggered or bubbling through its target to itself.
NOTE: This feature requires an options.render be set to a build Renderer object.
This allows for some interesting behavior. If you made a camera a Node you could attach children to the camera and use them as a menuing system that is not affected by the render order of the scene tree (they are always rendered last).
Aside from proxying events, there are some events that the camera does trigger.
on read returns node or null
on write Unbinds "*" and the node.eventProxy handler from the old target and binds "*" to the incoming node
This is an extendable method who's default implementation takes an incoming event, and node.triggers it on node.
return the result of the node.trigger
This method uses the options.renderer's render method to render the camera's current target. Once this is done, options.renderer.renderTree is called on all of node.children if available.
This feature allows nodes to be "picked up" and "dragged" around the canvas. It works by binding to the "mouse.move", "mouse.down", and "mouse.up" events, and triggering it's own events.
Sets up the node's offset to the mouse coordinates.
return Draggable Object
Sets node.x and node.y based on the difference between the old node position, the current mouse position, and the originally calculated offset.
return undefined
Resets the node's offset to 0,0
return undefined
Enables dropping of draggable elements onto the target node. This feature emits a various events upon different states of the drop action.
This method inspects source and returns whether or not it is allowed to be dropped in node
source the incoming object
mouse contains the current x and y coordinates of the mouse
return true if allowed and false if denied
This is an extendable handler for the "drop.start" event. By default it does nothing
source the incoming object
mouse contains the current x and y coordinates of the mouse
This is an extendable handler for the "drop.move" event. By default it does nothing node
source the incoming object
mouse contains the current x and y coordinates of the mouse
This is an extendable handler for the "drop.end" event. By default it reparents
the source object to node
source the incoming object
mouse contains the current x and y coordinates of the mouse
Manages in-flight Draggable objects and attempts to link them up with DropTargets by utilizing node.nodesByPoint to collect nodes that the Draggable is currently residing over
When a "drag.end" event is detected, the list of DropTargets is evaluated by calling node.dropFilter and finding the best match for the drop. If an acceptable DropTarget is found, a "drop.end" event is triggered on the found node
This feature moves a node whenever it's parent node moves. Re-parenting a node will unbind from the current parent and bind to the new parent. How the child reacts to the parent's movement is controlled by the positionChange method.
This is an event handler for parent.x and parent.y changes. name is the name of the event, and data contains the following properties:
Previous value of the parent's x or y (use name to determine which)
Current value of the parent's x or y (use name to determine which)
Provider for evented CSS properties. Provides a node.style property which Currently supports:
Provider of a CSS box model. This feature is meant to be stacked on the Style feature, and in fact, it requires it. Currently supports: