2012-03-07 13:45:48 +08:00
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// Node
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
|
|
* Node constructor. Nodes are entities that can move around
|
|
|
|
* and have events bound to them. They are the building blocks of a KineticJS
|
|
|
|
* application
|
|
|
|
* @constructor
|
|
|
|
* @param {Object} config
|
|
|
|
*/
|
|
|
|
Kinetic.Node = function(config) {
|
|
|
|
this.visible = true;
|
|
|
|
this.isListening = true;
|
|
|
|
this.name = undefined;
|
|
|
|
this.alpha = 1;
|
|
|
|
this.x = 0;
|
|
|
|
this.y = 0;
|
|
|
|
this.scale = {
|
|
|
|
x: 1,
|
|
|
|
y: 1
|
|
|
|
};
|
|
|
|
this.rotation = 0;
|
|
|
|
this.centerOffset = {
|
|
|
|
x: 0,
|
|
|
|
y: 0
|
|
|
|
};
|
|
|
|
this.eventListeners = {};
|
|
|
|
this.drag = {
|
|
|
|
x: false,
|
|
|
|
y: false
|
|
|
|
};
|
|
|
|
|
|
|
|
// set properties from config
|
|
|
|
if(config) {
|
|
|
|
for(var key in config) {
|
|
|
|
// handle special keys
|
|
|
|
switch (key) {
|
|
|
|
case "draggable":
|
|
|
|
this.draggable(config[key]);
|
|
|
|
break;
|
|
|
|
case "draggableX":
|
|
|
|
this.draggableX(config[key]);
|
|
|
|
break;
|
|
|
|
case "draggableY":
|
|
|
|
this.draggableY(config[key]);
|
|
|
|
break;
|
|
|
|
case "listen":
|
|
|
|
this.listen(config[key]);
|
|
|
|
break;
|
|
|
|
case "rotationDeg":
|
|
|
|
this.rotation = config[key] * Math.PI / 180;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
this[key] = config[key];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// overrides
|
|
|
|
if(this.centerOffset.x === undefined) {
|
|
|
|
this.centerOffset.x = 0;
|
|
|
|
}
|
|
|
|
if(this.centerOffset.y === undefined) {
|
|
|
|
this.centerOffset.y = 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
/*
|
|
|
|
* Node methods
|
|
|
|
*/
|
|
|
|
Kinetic.Node.prototype = {
|
|
|
|
/**
|
|
|
|
* bind events to the node. KineticJS supports mouseover, mousemove,
|
|
|
|
* mouseout, mousedown, mouseup, click, dblclick, touchstart, touchmove,
|
|
|
|
* touchend, dbltap, dragstart, dragmove, and dragend. Pass in a string
|
|
|
|
* of event types delimmited by a space to bind multiple events at once
|
|
|
|
* such as "mousedown mouseup mousemove". include a namespace to bind an
|
|
|
|
* event by name such as "click.foobar".
|
|
|
|
* @param {String} typesStr
|
|
|
|
* @param {function} handler
|
|
|
|
*/
|
|
|
|
on: function(typesStr, handler) {
|
|
|
|
var types = typesStr.split(" ");
|
|
|
|
/*
|
|
|
|
* loop through types and attach event listeners to
|
|
|
|
* each one. eg. "click mouseover.namespace mouseout"
|
|
|
|
* will create three event bindings
|
|
|
|
*/
|
|
|
|
for(var n = 0; n < types.length; n++) {
|
|
|
|
var type = types[n];
|
|
|
|
var event = (type.indexOf('touch') === -1) ? 'on' + type : type;
|
|
|
|
var parts = event.split(".");
|
|
|
|
var baseEvent = parts[0];
|
|
|
|
var name = parts.length > 1 ? parts[1] : "";
|
|
|
|
|
|
|
|
if(!this.eventListeners[baseEvent]) {
|
|
|
|
this.eventListeners[baseEvent] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
this.eventListeners[baseEvent].push({
|
|
|
|
name: name,
|
|
|
|
handler: handler
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* remove event bindings from the node. Pass in a string of
|
|
|
|
* event types delimmited by a space to remove multiple event
|
|
|
|
* bindings at once such as "mousedown mouseup mousemove".
|
|
|
|
* include a namespace to remove an event binding by name
|
|
|
|
* such as "click.foobar".
|
|
|
|
* @param {String} typesStr
|
|
|
|
*/
|
|
|
|
off: function(typesStr) {
|
|
|
|
var types = typesStr.split(" ");
|
|
|
|
|
|
|
|
for(var n = 0; n < types.length; n++) {
|
|
|
|
var type = types[n];
|
|
|
|
var event = (type.indexOf('touch') === -1) ? 'on' + type : type;
|
|
|
|
var parts = event.split(".");
|
|
|
|
var baseEvent = parts[0];
|
|
|
|
|
|
|
|
if(this.eventListeners[baseEvent] && parts.length > 1) {
|
|
|
|
var name = parts[1];
|
|
|
|
|
|
|
|
for(var i = 0; i < this.eventListeners[baseEvent].length; i++) {
|
|
|
|
if(this.eventListeners[baseEvent][i].name === name) {
|
|
|
|
this.eventListeners[baseEvent].splice(i, 1);
|
|
|
|
if(this.eventListeners[baseEvent].length === 0) {
|
|
|
|
this.eventListeners[baseEvent] = undefined;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
this.eventListeners[baseEvent] = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* show node
|
|
|
|
*/
|
|
|
|
show: function() {
|
|
|
|
this.visible = true;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* hide node
|
|
|
|
*/
|
|
|
|
hide: function() {
|
|
|
|
this.visible = false;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get zIndex
|
|
|
|
*/
|
|
|
|
getZIndex: function() {
|
|
|
|
return this.index;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set node scale. If only one parameter is passed in,
|
|
|
|
* then both scaleX and scaleY are set with that parameter
|
|
|
|
* @param {Number} scaleX
|
|
|
|
* @param {Number} scaleY
|
|
|
|
*/
|
|
|
|
setScale: function(scaleX, scaleY) {
|
|
|
|
if(scaleY) {
|
|
|
|
this.scale.x = scaleX;
|
|
|
|
this.scale.y = scaleY;
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
this.scale.x = scaleX;
|
|
|
|
this.scale.y = scaleX;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get scale
|
|
|
|
*/
|
|
|
|
getScale: function() {
|
|
|
|
return this.scale;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set node position
|
|
|
|
* @param {Number} x
|
|
|
|
* @param {Number} y
|
|
|
|
*/
|
|
|
|
setPosition: function(x, y) {
|
|
|
|
this.x = x;
|
|
|
|
this.y = y;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get node position relative to container
|
|
|
|
*/
|
|
|
|
getPosition: function() {
|
|
|
|
return {
|
|
|
|
x: this.x,
|
|
|
|
y: this.y
|
|
|
|
};
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get absolute position relative to stage
|
|
|
|
*/
|
|
|
|
getAbsolutePosition: function() {
|
|
|
|
var x = this.x;
|
|
|
|
var y = this.y;
|
|
|
|
var parent = this.getParent();
|
|
|
|
while(parent.className !== "Stage") {
|
|
|
|
x += parent.x;
|
|
|
|
y += parent.y;
|
|
|
|
parent = parent.parent;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
x: x,
|
|
|
|
y: y
|
|
|
|
};
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node by an amount
|
|
|
|
* @param {Number} x
|
|
|
|
* @param {Number} y
|
|
|
|
*/
|
|
|
|
move: function(x, y) {
|
|
|
|
this.x += x;
|
|
|
|
this.y += y;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set node rotation in radians
|
|
|
|
* @param {Number} theta
|
|
|
|
*/
|
|
|
|
setRotation: function(theta) {
|
|
|
|
this.rotation = theta;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set node rotation in degrees
|
|
|
|
* @param {Number} deg
|
|
|
|
*/
|
|
|
|
setRotationDeg: function(deg) {
|
|
|
|
this.rotation = (deg * Math.PI / 180);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get rotation in radians
|
|
|
|
*/
|
|
|
|
getRotation: function() {
|
|
|
|
return this.rotation;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get rotation in degrees
|
|
|
|
*/
|
|
|
|
getRotationDeg: function() {
|
|
|
|
return this.rotation * 180 / Math.PI;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* rotate node by an amount in radians
|
|
|
|
* @param {Number} theta
|
|
|
|
*/
|
|
|
|
rotate: function(theta) {
|
|
|
|
this.rotation += theta;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* rotate node by an amount in degrees
|
|
|
|
* @param {Number} deg
|
|
|
|
*/
|
|
|
|
rotateDeg: function(deg) {
|
|
|
|
this.rotation += (deg * Math.PI / 180);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* listen or don't listen to events
|
|
|
|
* @param {Boolean} isListening
|
|
|
|
*/
|
|
|
|
listen: function(isListening) {
|
|
|
|
this.isListening = isListening;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node to top
|
|
|
|
*/
|
|
|
|
moveToTop: function() {
|
|
|
|
var index = this.index;
|
|
|
|
this.parent.children.splice(index, 1);
|
|
|
|
this.parent.children.push(this);
|
|
|
|
this.parent._setChildrenIndices();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node up
|
|
|
|
*/
|
|
|
|
moveUp: function() {
|
|
|
|
var index = this.index;
|
|
|
|
this.parent.children.splice(index, 1);
|
|
|
|
this.parent.children.splice(index + 1, 0, this);
|
|
|
|
this.parent._setChildrenIndices();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node down
|
|
|
|
*/
|
|
|
|
moveDown: function() {
|
|
|
|
var index = this.index;
|
|
|
|
if(index > 0) {
|
|
|
|
this.parent.children.splice(index, 1);
|
|
|
|
this.parent.children.splice(index - 1, 0, this);
|
|
|
|
this.parent._setChildrenIndices();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node to bottom
|
|
|
|
*/
|
|
|
|
moveToBottom: function() {
|
|
|
|
var index = this.index;
|
|
|
|
this.parent.children.splice(index, 1);
|
|
|
|
this.parent.children.unshift(this);
|
|
|
|
this.parent._setChildrenIndices();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set zIndex
|
2012-03-12 14:01:23 +08:00
|
|
|
* @param {int} zIndex
|
2012-03-07 13:45:48 +08:00
|
|
|
*/
|
|
|
|
setZIndex: function(zIndex) {
|
|
|
|
var index = this.index;
|
|
|
|
this.parent.children.splice(index, 1);
|
|
|
|
this.parent.children.splice(zIndex, 0, this);
|
|
|
|
this.parent._setChildrenIndices();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set alpha. Alpha values range from 0 to 1.
|
|
|
|
* A node with an alpha of 0 is fully transparent, and a node
|
|
|
|
* with an alpha of 1 is fully opaque
|
|
|
|
* @param {Object} alpha
|
|
|
|
*/
|
|
|
|
setAlpha: function(alpha) {
|
|
|
|
this.alpha = alpha;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get alpha. Alpha values range from 0 to 1.
|
|
|
|
* A node with an alpha of 0 is fully transparent, and a node
|
|
|
|
* with an alpha of 1 is fully opaque
|
|
|
|
*/
|
|
|
|
getAlpha: function() {
|
|
|
|
return this.alpha;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get absolute alpha
|
|
|
|
*/
|
|
|
|
getAbsoluteAlpha: function() {
|
|
|
|
var absAlpha = 1;
|
|
|
|
var node = this;
|
|
|
|
// traverse upwards
|
|
|
|
while(node.className !== "Stage") {
|
|
|
|
absAlpha *= node.alpha;
|
|
|
|
node = node.parent;
|
|
|
|
}
|
|
|
|
return absAlpha;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* enable or disable drag and drop
|
|
|
|
* @param {Boolean} setDraggable
|
|
|
|
*/
|
|
|
|
draggable: function(setDraggable) {
|
|
|
|
if(setDraggable) {
|
|
|
|
var needInit = !this.drag.x && !this.drag.y;
|
|
|
|
this.drag.x = true;
|
|
|
|
this.drag.y = true;
|
|
|
|
|
|
|
|
if(needInit) {
|
|
|
|
this._initDrag();
|
|
|
|
}
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
this.drag.x = false;
|
|
|
|
this.drag.y = false;
|
|
|
|
this._dragCleanup();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* enable or disable horizontal drag and drop
|
|
|
|
* @param {Boolean} setDraggable
|
|
|
|
*/
|
|
|
|
draggableX: function(setDraggable) {
|
|
|
|
if(setDraggable) {
|
|
|
|
var needInit = !this.drag.x && !this.drag.y;
|
|
|
|
this.drag.x = true;
|
|
|
|
if(needInit) {
|
|
|
|
this._initDrag();
|
|
|
|
}
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
this.drag.x = false;
|
|
|
|
this._dragCleanup();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* enable or disable vertical drag and drop
|
|
|
|
* @param {Boolean} setDraggable
|
|
|
|
*/
|
|
|
|
draggableY: function(setDraggable) {
|
|
|
|
if(setDraggable) {
|
|
|
|
var needInit = !this.drag.x && !this.drag.y;
|
|
|
|
this.drag.y = true;
|
|
|
|
if(needInit) {
|
|
|
|
this._initDrag();
|
|
|
|
}
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
this.drag.y = false;
|
|
|
|
this._dragCleanup();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* determine if node is currently in drag and drop mode
|
|
|
|
*/
|
|
|
|
isDragging: function() {
|
|
|
|
var go = Kinetic.GlobalObject;
|
|
|
|
return go.drag.node !== undefined && go.drag.node.id === this.id && go.drag.moving;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* move node to another container
|
2012-03-12 14:01:23 +08:00
|
|
|
* @param {Container} newContainer
|
2012-03-07 13:45:48 +08:00
|
|
|
*/
|
|
|
|
moveTo: function(newContainer) {
|
|
|
|
var parent = this.parent;
|
|
|
|
// remove from parent's children
|
|
|
|
parent.children.splice(this.index, 1);
|
|
|
|
parent._setChildrenIndices();
|
|
|
|
|
|
|
|
// add to new parent
|
|
|
|
newContainer.children.push(this);
|
|
|
|
this.index = newContainer.children.length - 1;
|
|
|
|
this.parent = newContainer;
|
|
|
|
newContainer._setChildrenIndices();
|
|
|
|
|
|
|
|
// update children hashes
|
|
|
|
if(this.name) {
|
|
|
|
parent.childrenNames[this.name] = undefined;
|
|
|
|
newContainer.childrenNames[this.name] = this;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get parent container
|
|
|
|
*/
|
|
|
|
getParent: function() {
|
|
|
|
return this.parent;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get layer associated to node
|
|
|
|
*/
|
|
|
|
getLayer: function() {
|
|
|
|
if(this.className === 'Layer') {
|
|
|
|
return this;
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-07 13:45:48 +08:00
|
|
|
return this.getParent().getLayer();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get stage associated to node
|
|
|
|
*/
|
|
|
|
getStage: function() {
|
2012-03-13 13:41:09 +08:00
|
|
|
if(this.className === 'Stage') {
|
|
|
|
return this;
|
2012-03-14 12:16:25 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-03-13 13:41:09 +08:00
|
|
|
return this.getParent().getStage();
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get name
|
|
|
|
*/
|
|
|
|
getName: function() {
|
|
|
|
return this.name;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set center offset
|
|
|
|
* @param {Number} x
|
|
|
|
* @param {Number} y
|
|
|
|
*/
|
|
|
|
setCenterOffset: function(x, y) {
|
|
|
|
this.centerOffset.x = x;
|
|
|
|
this.centerOffset.y = y;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get center offset
|
|
|
|
*/
|
|
|
|
getCenterOffset: function() {
|
|
|
|
return this.centerOffset;
|
|
|
|
},
|
2012-03-13 13:41:09 +08:00
|
|
|
/**
|
2012-03-14 12:16:25 +08:00
|
|
|
* transition node to another state. Any property that can accept a real
|
|
|
|
* number such as x, y, rotation, alpha, strokeWidth, radius, scale.x, scale.y,
|
|
|
|
* centerOffset.x and centerOffset.y can be transitioned
|
2012-03-13 13:41:09 +08:00
|
|
|
* @param {Object} config
|
|
|
|
*/
|
|
|
|
transitionTo: function(config) {
|
|
|
|
var layer = this.getLayer();
|
|
|
|
var that = this;
|
2012-03-14 12:16:25 +08:00
|
|
|
var duration = config.duration * 1000;
|
2012-03-13 13:41:09 +08:00
|
|
|
var changes = {};
|
2012-03-14 12:16:25 +08:00
|
|
|
|
2012-03-13 13:41:09 +08:00
|
|
|
for(var key in config) {
|
|
|
|
if(config.hasOwnProperty(key)) {
|
2012-03-14 12:16:25 +08:00
|
|
|
if(config[key].x !== undefined || config[key].y !== undefined) {
|
|
|
|
changes[key] = {};
|
|
|
|
var propArray = ["x", "y"];
|
|
|
|
for(var n = 0; n < propArray.length; n++) {
|
|
|
|
var prop = propArray[n];
|
|
|
|
if(config[key][prop] !== undefined) {
|
|
|
|
changes[key][prop] = (config[key][prop] - that[key][prop]) / duration;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
changes[key] = (config[key] - that[key]) / duration;
|
|
|
|
}
|
2012-03-13 13:41:09 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
layer.transitions.push({
|
|
|
|
id: layer.transitionIdCounter++,
|
|
|
|
time: 0,
|
|
|
|
config: config,
|
|
|
|
node: this,
|
|
|
|
changes: changes
|
|
|
|
});
|
|
|
|
},
|
2012-03-07 13:45:48 +08:00
|
|
|
/**
|
|
|
|
* initialize drag and drop
|
|
|
|
*/
|
|
|
|
_initDrag: function() {
|
|
|
|
var go = Kinetic.GlobalObject;
|
|
|
|
var that = this;
|
|
|
|
this.on("mousedown.initdrag touchstart.initdrag", function(evt) {
|
|
|
|
var stage = that.getStage();
|
|
|
|
var pos = stage.getUserPosition();
|
|
|
|
|
|
|
|
if(pos) {
|
|
|
|
go.drag.node = that;
|
|
|
|
go.drag.offset.x = pos.x - that.x;
|
|
|
|
go.drag.offset.y = pos.y - that.y;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* remove drag and drop event listener
|
|
|
|
*/
|
|
|
|
_dragCleanup: function() {
|
|
|
|
if(!this.drag.x && !this.drag.y) {
|
|
|
|
this.off("mousedown.initdrag");
|
|
|
|
this.off("touchstart.initdrag");
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* handle node events
|
|
|
|
* @param {String} eventType
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
_handleEvents: function(eventType, evt) {
|
|
|
|
// generic events handler
|
|
|
|
function handle(obj) {
|
|
|
|
var el = obj.eventListeners;
|
|
|
|
if(el[eventType]) {
|
|
|
|
var events = el[eventType];
|
|
|
|
for(var i = 0; i < events.length; i++) {
|
|
|
|
events[i].handler.apply(obj, [evt]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(obj.parent.className !== "Stage") {
|
|
|
|
handle(obj.parent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* simulate bubbling by handling node events
|
|
|
|
* first, followed by group events, followed
|
|
|
|
* by layer events
|
|
|
|
*/
|
|
|
|
handle(this);
|
|
|
|
}
|
2012-03-12 14:01:23 +08:00
|
|
|
};
|