2012-03-07 13:45:48 +08:00
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
// Stage
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
|
|
* Stage constructor. A stage is used to contain multiple layers and handle
|
|
|
|
* animations
|
|
|
|
* @constructor
|
2012-03-19 10:50:20 +08:00
|
|
|
* @augments Kinetic.Container
|
2012-03-23 04:47:37 +08:00
|
|
|
* @augments Kinetic.Node
|
2012-03-07 13:45:48 +08:00
|
|
|
* @param {String|DomElement} cont Container id or DOM element
|
|
|
|
* @param {int} width
|
|
|
|
* @param {int} height
|
|
|
|
*/
|
2012-03-23 14:17:52 +08:00
|
|
|
Kinetic.Stage = function(config) {
|
|
|
|
/*
|
|
|
|
* if container is a string, assume it's an id for
|
|
|
|
* a DOM element
|
|
|
|
*/
|
2012-03-24 14:39:54 +08:00
|
|
|
if( typeof config.container === 'string') {
|
2012-03-23 14:17:52 +08:00
|
|
|
config.container = document.getElementById(config.container);
|
|
|
|
}
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.className = 'Stage';
|
2012-03-23 14:17:52 +08:00
|
|
|
this.container = config.container;
|
2012-03-18 01:28:25 +08:00
|
|
|
this.content = document.createElement('div');
|
|
|
|
|
2012-03-23 14:17:52 +08:00
|
|
|
this.width = config.width;
|
|
|
|
this.height = config.height;
|
2012-03-07 13:45:48 +08:00
|
|
|
this.scale = {
|
|
|
|
x: 1,
|
|
|
|
y: 1
|
|
|
|
};
|
|
|
|
this.dblClickWindow = 400;
|
|
|
|
this.clickStart = false;
|
2012-03-19 02:24:57 +08:00
|
|
|
this.targetShape = undefined;
|
2012-03-11 08:52:16 +08:00
|
|
|
this.targetFound = false;
|
2012-03-19 02:24:57 +08:00
|
|
|
this.mouseoverShape = undefined;
|
|
|
|
this.mouseoutShape = undefined;
|
2012-03-07 13:45:48 +08:00
|
|
|
|
|
|
|
// desktop flags
|
|
|
|
this.mousePos = undefined;
|
|
|
|
this.mouseDown = false;
|
|
|
|
this.mouseUp = false;
|
|
|
|
|
|
|
|
// mobile flags
|
|
|
|
this.touchPos = undefined;
|
|
|
|
this.touchStart = false;
|
|
|
|
this.touchEnd = false;
|
|
|
|
|
2012-03-13 13:41:09 +08:00
|
|
|
// set stage id
|
|
|
|
this.id = Kinetic.GlobalObject.idCounter++;
|
|
|
|
|
|
|
|
// animation support
|
|
|
|
this.isAnimating = false;
|
|
|
|
this.onFrameFunc = undefined;
|
|
|
|
|
2012-03-18 01:48:25 +08:00
|
|
|
this._buildDOM();
|
2012-03-07 13:45:48 +08:00
|
|
|
this._listen();
|
|
|
|
this._prepareDrag();
|
|
|
|
|
|
|
|
// add stage to global object
|
2012-03-18 01:28:25 +08:00
|
|
|
Kinetic.GlobalObject.stages.push(this);
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-23 04:47:37 +08:00
|
|
|
// call super constructors
|
2012-03-07 13:45:48 +08:00
|
|
|
Kinetic.Container.apply(this, []);
|
2012-03-23 14:17:52 +08:00
|
|
|
Kinetic.Node.apply(this, [config]);
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
|
|
|
/*
|
|
|
|
* Stage methods
|
|
|
|
*/
|
|
|
|
Kinetic.Stage.prototype = {
|
|
|
|
/**
|
|
|
|
* sets onFrameFunc for animation
|
|
|
|
* @param {function} func
|
|
|
|
*/
|
|
|
|
onFrame: function(func) {
|
|
|
|
this.onFrameFunc = func;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* start animation
|
|
|
|
*/
|
|
|
|
start: function() {
|
|
|
|
this.isAnimating = true;
|
|
|
|
Kinetic.GlobalObject._handleAnimation();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* stop animation
|
|
|
|
*/
|
|
|
|
stop: function() {
|
|
|
|
this.isAnimating = false;
|
|
|
|
Kinetic.GlobalObject._handleAnimation();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* draw children
|
|
|
|
*/
|
|
|
|
draw: function() {
|
|
|
|
this._drawChildren();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set stage size
|
|
|
|
* @param {int} width
|
|
|
|
* @param {int} height
|
|
|
|
*/
|
|
|
|
setSize: function(width, height) {
|
|
|
|
var layers = this.children;
|
|
|
|
for(var n = 0; n < layers.length; n++) {
|
|
|
|
var layer = layers[n];
|
|
|
|
layer.getCanvas().width = width;
|
|
|
|
layer.getCanvas().height = height;
|
|
|
|
layer.draw();
|
|
|
|
}
|
|
|
|
|
|
|
|
// set stage dimensions
|
|
|
|
this.width = width;
|
|
|
|
this.height = height;
|
|
|
|
|
2012-04-01 15:31:02 +08:00
|
|
|
// set buffer layer and path layer sizes
|
2012-03-07 13:45:48 +08:00
|
|
|
this.bufferLayer.getCanvas().width = width;
|
|
|
|
this.bufferLayer.getCanvas().height = height;
|
2012-04-01 15:31:02 +08:00
|
|
|
this.pathLayer.getCanvas().width = width;
|
|
|
|
this.pathLayer.getCanvas().height = height;
|
2012-03-07 13:45:48 +08:00
|
|
|
},
|
2012-03-31 15:08:50 +08:00
|
|
|
/**
|
|
|
|
* return stage size
|
|
|
|
*/
|
|
|
|
getSize: function() {
|
|
|
|
return {
|
|
|
|
width: this.width,
|
|
|
|
height: this.height
|
|
|
|
};
|
|
|
|
},
|
2012-03-07 13:45:48 +08:00
|
|
|
/**
|
|
|
|
* clear all layers
|
|
|
|
*/
|
|
|
|
clear: function() {
|
|
|
|
var layers = this.children;
|
|
|
|
for(var n = 0; n < layers.length; n++) {
|
|
|
|
layers[n].clear();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
2012-03-22 20:59:29 +08:00
|
|
|
* Creates a composite data URL and passes it to a callback. If MIME type is not
|
2012-03-22 21:15:31 +08:00
|
|
|
* specified, then "image/png" will result. For "image/jpeg", specify a quality
|
2012-03-24 15:08:08 +08:00
|
|
|
* level as quality (range 0.0 - 1.0)
|
2012-03-07 13:45:48 +08:00
|
|
|
* @param {function} callback
|
2012-03-22 21:15:31 +08:00
|
|
|
* @param {String} mimeType (optional)
|
2012-03-24 15:08:08 +08:00
|
|
|
* @param {Number} quality (optional)
|
2012-03-07 13:45:48 +08:00
|
|
|
*/
|
2012-03-24 15:08:08 +08:00
|
|
|
toDataURL: function(callback, mimeType, quality) {
|
2012-03-07 13:45:48 +08:00
|
|
|
var bufferLayer = this.bufferLayer;
|
|
|
|
var bufferContext = bufferLayer.getContext();
|
|
|
|
var layers = this.children;
|
|
|
|
|
|
|
|
function addLayer(n) {
|
|
|
|
var dataURL = layers[n].getCanvas().toDataURL();
|
|
|
|
var imageObj = new Image();
|
|
|
|
imageObj.onload = function() {
|
|
|
|
bufferContext.drawImage(this, 0, 0);
|
|
|
|
n++;
|
|
|
|
if(n < layers.length) {
|
|
|
|
addLayer(n);
|
2012-03-14 13:11:22 +08:00
|
|
|
}
|
|
|
|
else {
|
2012-04-01 23:00:58 +08:00
|
|
|
try {
|
|
|
|
// If this call fails (due to browser bug, like in Firefox 3.6),
|
|
|
|
// then revert to previous no-parameter image/png behavior
|
|
|
|
callback(bufferLayer.getCanvas().toDataURL(mimeType, quality));
|
|
|
|
}
|
2012-04-02 01:29:16 +08:00
|
|
|
catch(exception) {
|
2012-04-01 23:00:58 +08:00
|
|
|
callback(bufferLayer.getCanvas().toDataURL());
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
imageObj.src = dataURL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bufferLayer.clear();
|
|
|
|
addLayer(0);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* remove layer from stage
|
|
|
|
* @param {Layer} layer
|
|
|
|
*/
|
|
|
|
remove: function(layer) {
|
2012-04-02 01:29:16 +08:00
|
|
|
/*
|
|
|
|
* remove canvas DOM from the document if
|
|
|
|
* it exists
|
|
|
|
*/
|
|
|
|
try {
|
|
|
|
this.content.removeChild(layer.canvas);
|
|
|
|
}
|
|
|
|
catch(e) {
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
this._remove(layer);
|
|
|
|
},
|
|
|
|
/**
|
2012-03-23 04:47:37 +08:00
|
|
|
* bind event listener to container DOM element
|
2012-03-07 13:45:48 +08:00
|
|
|
* @param {String} typesStr
|
|
|
|
* @param {function} handler
|
|
|
|
*/
|
2012-03-23 04:47:37 +08:00
|
|
|
onContainer: function(typesStr, handler) {
|
2012-03-18 01:28:25 +08:00
|
|
|
var types = typesStr.split(' ');
|
2012-03-07 13:45:48 +08:00
|
|
|
for(var n = 0; n < types.length; n++) {
|
|
|
|
var baseEvent = types[n];
|
|
|
|
this.container.addEventListener(baseEvent, handler, false);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* add layer to stage
|
|
|
|
* @param {Layer} layer
|
|
|
|
*/
|
|
|
|
add: function(layer) {
|
|
|
|
if(layer.name) {
|
|
|
|
this.childrenNames[layer.name] = layer;
|
|
|
|
}
|
|
|
|
layer.canvas.width = this.width;
|
|
|
|
layer.canvas.height = this.height;
|
|
|
|
this._add(layer);
|
|
|
|
|
|
|
|
// draw layer and append canvas to container
|
|
|
|
layer.draw();
|
2012-03-18 01:28:25 +08:00
|
|
|
this.content.appendChild(layer.canvas);
|
2012-03-07 13:45:48 +08:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get mouse position for desktop apps
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
getMousePosition: function(evt) {
|
|
|
|
return this.mousePos;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get touch position for mobile apps
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
getTouchPosition: function(evt) {
|
|
|
|
return this.touchPos;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get user position (mouse position or touch position)
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
getUserPosition: function(evt) {
|
|
|
|
return this.getTouchPosition() || this.getMousePosition();
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get container DOM element
|
|
|
|
*/
|
|
|
|
getContainer: function() {
|
|
|
|
return this.container;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get stage
|
|
|
|
*/
|
|
|
|
getStage: function() {
|
|
|
|
return this;
|
|
|
|
},
|
|
|
|
/**
|
2012-03-11 08:52:16 +08:00
|
|
|
* detect event
|
|
|
|
* @param {Shape} shape
|
2012-03-07 13:45:48 +08:00
|
|
|
*/
|
2012-03-11 08:52:16 +08:00
|
|
|
_detectEvent: function(shape, evt) {
|
|
|
|
var isDragging = Kinetic.GlobalObject.drag.moving;
|
|
|
|
var go = Kinetic.GlobalObject;
|
|
|
|
var pos = this.getUserPosition();
|
|
|
|
var el = shape.eventListeners;
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
if(this.targetShape && shape.id === this.targetShape.id) {
|
|
|
|
this.targetFound = true;
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-04-01 16:08:33 +08:00
|
|
|
if(shape.visible && pos !== undefined && shape._isPointInShape(pos)) {
|
2012-03-11 08:52:16 +08:00
|
|
|
// handle onmousedown
|
|
|
|
if(!isDragging && this.mouseDown) {
|
|
|
|
this.mouseDown = false;
|
|
|
|
this.clickStart = true;
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('onmousedown', evt);
|
2012-03-11 08:52:16 +08:00
|
|
|
return true;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
2012-03-11 08:52:16 +08:00
|
|
|
// handle onmouseup & onclick
|
2012-03-18 05:35:34 +08:00
|
|
|
else if(this.mouseUp) {
|
2012-03-11 08:52:16 +08:00
|
|
|
this.mouseUp = false;
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('onmouseup', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
// detect if click or double click occurred
|
|
|
|
if(this.clickStart) {
|
|
|
|
/*
|
|
|
|
* if dragging and dropping, don't fire click or dbl click
|
|
|
|
* event
|
|
|
|
*/
|
|
|
|
if((!go.drag.moving) || !go.drag.node) {
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('onclick', evt);
|
2012-03-11 08:52:16 +08:00
|
|
|
|
|
|
|
if(shape.inDoubleClickWindow) {
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('ondblclick', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
2012-03-11 08:52:16 +08:00
|
|
|
shape.inDoubleClickWindow = true;
|
|
|
|
setTimeout(function() {
|
|
|
|
shape.inDoubleClickWindow = false;
|
|
|
|
}, this.dblClickWindow);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
}
|
2012-03-11 08:52:16 +08:00
|
|
|
return true;
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
// handle touchstart
|
2012-03-18 05:35:34 +08:00
|
|
|
else if(this.touchStart) {
|
2012-03-11 08:52:16 +08:00
|
|
|
this.touchStart = false;
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('touchstart', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
if(el.ondbltap && shape.inDoubleClickWindow) {
|
|
|
|
var events = el.ondbltap;
|
|
|
|
for(var i = 0; i < events.length; i++) {
|
|
|
|
events[i].handler.apply(shape, [evt]);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
shape.inDoubleClickWindow = true;
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
setTimeout(function() {
|
|
|
|
shape.inDoubleClickWindow = false;
|
|
|
|
}, this.dblClickWindow);
|
|
|
|
return true;
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
// handle touchend
|
2012-03-18 05:35:34 +08:00
|
|
|
else if(this.touchEnd) {
|
2012-03-11 08:52:16 +08:00
|
|
|
this.touchEnd = false;
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('touchend', evt);
|
2012-03-11 08:52:16 +08:00
|
|
|
return true;
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-18 10:48:54 +08:00
|
|
|
/*
|
2012-03-19 02:24:57 +08:00
|
|
|
* NOTE: these event handlers require target shape
|
|
|
|
* handling
|
|
|
|
*/
|
|
|
|
|
|
|
|
// handle onmouseover
|
2012-03-18 10:48:54 +08:00
|
|
|
else if(!isDragging && this._isNewTarget(shape, evt)) {
|
2012-03-19 02:24:57 +08:00
|
|
|
/*
|
|
|
|
* check to see if there are stored mouseout events first.
|
|
|
|
* if there are, run those before running the onmouseover
|
|
|
|
* events
|
|
|
|
*/
|
|
|
|
if(this.mouseoutShape) {
|
|
|
|
this.mouseoverShape = shape;
|
|
|
|
this.mouseoutShape._handleEvents('onmouseout', evt);
|
|
|
|
this.mouseoverShape = undefined;
|
|
|
|
}
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('onmouseover', evt);
|
2012-03-19 02:24:57 +08:00
|
|
|
this._setTarget(shape);
|
2012-03-07 13:45:48 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-03-18 10:48:54 +08:00
|
|
|
// handle mousemove and touchmove
|
2012-03-18 05:35:34 +08:00
|
|
|
else if(!isDragging) {
|
2012-03-18 01:28:25 +08:00
|
|
|
shape._handleEvents('onmousemove', evt);
|
2012-03-18 10:48:54 +08:00
|
|
|
shape._handleEvents('touchmove', evt);
|
2012-03-11 08:52:16 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// handle mouseout condition
|
2012-03-18 05:35:34 +08:00
|
|
|
else if(!isDragging && this.targetShape && this.targetShape.id === shape.id) {
|
2012-03-19 02:24:57 +08:00
|
|
|
this._setTarget(undefined);
|
|
|
|
this.mouseoutShape = shape;
|
|
|
|
//shape._handleEvents('onmouseout', evt);
|
2012-03-11 08:52:16 +08:00
|
|
|
return true;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
return false;
|
|
|
|
},
|
2012-03-19 02:24:57 +08:00
|
|
|
/**
|
|
|
|
* set new target
|
|
|
|
*/
|
|
|
|
_setTarget: function(shape) {
|
|
|
|
this.targetShape = shape;
|
|
|
|
this.targetFound = true;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* check if shape should be a new target
|
|
|
|
*/
|
2012-03-18 10:48:54 +08:00
|
|
|
_isNewTarget: function(shape, evt) {
|
|
|
|
if(!this.targetShape || (!this.targetFound && shape.id !== this.targetShape.id)) {
|
|
|
|
/*
|
|
|
|
* check if old target has an onmouseout event listener
|
|
|
|
*/
|
|
|
|
if(this.targetShape) {
|
|
|
|
var oldEl = this.targetShape.eventListeners;
|
|
|
|
if(oldEl) {
|
2012-03-19 02:24:57 +08:00
|
|
|
this.mouseoutShape = this.targetShape;
|
|
|
|
//this.targetShape._handleEvents('onmouseout', evt);
|
2012-03-18 10:48:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
},
|
2012-03-11 08:52:16 +08:00
|
|
|
/**
|
|
|
|
* traverse container children
|
|
|
|
* @param {Container} obj
|
|
|
|
*/
|
|
|
|
_traverseChildren: function(obj, evt) {
|
|
|
|
var children = obj.children;
|
|
|
|
// propapgate backwards through children
|
|
|
|
for(var i = children.length - 1; i >= 0; i--) {
|
|
|
|
var child = children[i];
|
2012-03-24 15:08:08 +08:00
|
|
|
if(child.isListening) {
|
2012-03-22 21:15:31 +08:00
|
|
|
if(child.className === 'Shape') {
|
|
|
|
var exit = this._detectEvent(child, evt);
|
|
|
|
if(exit) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
var exit = this._traverseChildren(child, evt);
|
|
|
|
if(exit) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-11 08:52:16 +08:00
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
return false;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* handle incoming event
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
2012-03-23 04:47:37 +08:00
|
|
|
_handleStageEvent: function(evt) {
|
2012-03-11 08:52:16 +08:00
|
|
|
var go = Kinetic.GlobalObject;
|
|
|
|
if(!evt) {
|
|
|
|
evt = window.event;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
|
2012-03-11 08:52:16 +08:00
|
|
|
this._setMousePosition(evt);
|
|
|
|
this._setTouchPosition(evt);
|
|
|
|
|
2012-04-01 15:31:02 +08:00
|
|
|
this._clearDefaultLayers();
|
2012-03-11 08:52:16 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* loop through layers. If at any point an event
|
|
|
|
* is triggered, n is set to -1 which will break out of the
|
|
|
|
* three nested loops
|
|
|
|
*/
|
|
|
|
this.targetFound = false;
|
2012-03-19 02:24:57 +08:00
|
|
|
var shapeDetected = false;
|
2012-03-11 08:52:16 +08:00
|
|
|
for(var n = this.children.length - 1; n >= 0; n--) {
|
|
|
|
var layer = this.children[n];
|
|
|
|
if(layer.visible && n >= 0 && layer.isListening) {
|
|
|
|
if(this._traverseChildren(layer, evt)) {
|
|
|
|
n = -1;
|
2012-03-19 02:24:57 +08:00
|
|
|
shapeDetected = true;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-03-19 02:24:57 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* if no shape was detected and a mouseout shape has been stored,
|
|
|
|
* then run the onmouseout event handlers
|
|
|
|
*/
|
|
|
|
if(!shapeDetected && this.mouseoutShape) {
|
|
|
|
this.mouseoutShape._handleEvents('onmouseout', evt);
|
|
|
|
this.mouseoutShape = undefined;
|
|
|
|
|
|
|
|
}
|
2012-03-07 13:45:48 +08:00
|
|
|
},
|
2012-04-01 15:31:02 +08:00
|
|
|
/**
|
|
|
|
* clear default layers
|
|
|
|
*/
|
|
|
|
_clearDefaultLayers: function() {
|
2012-04-01 16:08:33 +08:00
|
|
|
this.bufferLayer.clear();
|
|
|
|
this.pathLayer.clear();
|
2012-04-01 15:31:02 +08:00
|
|
|
},
|
2012-03-07 13:45:48 +08:00
|
|
|
/**
|
|
|
|
* begin listening for events by adding event handlers
|
|
|
|
* to the container
|
|
|
|
*/
|
|
|
|
_listen: function() {
|
|
|
|
var that = this;
|
|
|
|
|
|
|
|
// desktop events
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('mousedown', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
that.mouseDown = true;
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('mousemove', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
that.mouseUp = false;
|
|
|
|
that.mouseDown = false;
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('mouseup', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
that.mouseUp = true;
|
|
|
|
that.mouseDown = false;
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
|
|
|
|
that.clickStart = false;
|
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('mouseover', function(evt) {
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('mouseout', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
that.mousePos = undefined;
|
|
|
|
}, false);
|
|
|
|
// mobile events
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('touchstart', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
evt.preventDefault();
|
|
|
|
that.touchStart = true;
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('touchmove', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
evt.preventDefault();
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
|
2012-03-18 01:28:25 +08:00
|
|
|
this.container.addEventListener('touchend', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
evt.preventDefault();
|
|
|
|
that.touchEnd = true;
|
2012-03-23 04:47:37 +08:00
|
|
|
that._handleStageEvent(evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}, false);
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set mouse positon for desktop apps
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
_setMousePosition: function(evt) {
|
2012-03-22 20:59:29 +08:00
|
|
|
var mouseX = evt.offsetX || (evt.clientX - this._getContainerPosition().left + window.pageXOffset);
|
|
|
|
var mouseY = evt.offsetY || (evt.clientY - this._getContainerPosition().top + window.pageYOffset);
|
2012-03-07 13:45:48 +08:00
|
|
|
this.mousePos = {
|
|
|
|
x: mouseX,
|
|
|
|
y: mouseY
|
|
|
|
};
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* set touch position for mobile apps
|
|
|
|
* @param {Event} evt
|
|
|
|
*/
|
|
|
|
_setTouchPosition: function(evt) {
|
|
|
|
if(evt.touches !== undefined && evt.touches.length === 1) {// Only deal with
|
|
|
|
// one finger
|
|
|
|
var touch = evt.touches[0];
|
|
|
|
// Get the information for finger #1
|
|
|
|
var touchX = touch.clientX - this._getContainerPosition().left + window.pageXOffset;
|
|
|
|
var touchY = touch.clientY - this._getContainerPosition().top + window.pageYOffset;
|
|
|
|
|
|
|
|
this.touchPos = {
|
|
|
|
x: touchX,
|
|
|
|
y: touchY
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* get container position
|
|
|
|
*/
|
|
|
|
_getContainerPosition: function() {
|
|
|
|
var obj = this.container;
|
|
|
|
var top = 0;
|
|
|
|
var left = 0;
|
2012-03-18 01:28:25 +08:00
|
|
|
while(obj && obj.tagName !== 'BODY') {
|
|
|
|
top += obj.offsetTop - obj.scrollTop;
|
|
|
|
left += obj.offsetLeft - obj.scrollLeft;
|
2012-03-07 13:45:48 +08:00
|
|
|
obj = obj.offsetParent;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
top: top,
|
|
|
|
left: left
|
|
|
|
};
|
|
|
|
},
|
|
|
|
/**
|
2012-04-01 15:31:02 +08:00
|
|
|
* modify path context
|
|
|
|
* @param {CanvasContext} context
|
2012-03-07 13:45:48 +08:00
|
|
|
*/
|
2012-04-01 15:31:02 +08:00
|
|
|
_modifyPathContext: function(context) {
|
|
|
|
context.stroke = function() {
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.fill = function() {
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.fillRect = function(x, y, width, height) {
|
2012-03-07 13:45:48 +08:00
|
|
|
layer.context.rect(x, y, width, height);
|
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.strokeRect = function(x, y, width, height) {
|
2012-03-07 13:45:48 +08:00
|
|
|
layer.context.rect(x, y, width, height);
|
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.drawImage = function() {
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.fillText = function() {
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
2012-04-01 15:31:02 +08:00
|
|
|
context.strokeText = function() {
|
2012-03-07 13:45:48 +08:00
|
|
|
};
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* end drag and drop
|
|
|
|
*/
|
|
|
|
_endDrag: function(evt) {
|
|
|
|
var go = Kinetic.GlobalObject;
|
|
|
|
if(go.drag.node) {
|
|
|
|
if(go.drag.moving) {
|
|
|
|
go.drag.moving = false;
|
2012-03-18 01:28:25 +08:00
|
|
|
go.drag.node._handleEvents('ondragend', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
go.drag.node = undefined;
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* prepare drag and drop
|
|
|
|
*/
|
|
|
|
_prepareDrag: function() {
|
|
|
|
var that = this;
|
|
|
|
|
2012-03-23 04:47:37 +08:00
|
|
|
this.onContainer('mousemove touchmove', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
var go = Kinetic.GlobalObject;
|
2012-03-14 13:11:22 +08:00
|
|
|
var node = go.drag.node;
|
|
|
|
if(node) {
|
2012-03-07 13:45:48 +08:00
|
|
|
var pos = that.getUserPosition();
|
2012-03-24 14:39:54 +08:00
|
|
|
var dc = node.dragConstraint;
|
2012-03-14 13:11:22 +08:00
|
|
|
var db = node.dragBounds;
|
2012-03-24 14:39:54 +08:00
|
|
|
|
2012-03-25 11:52:17 +08:00
|
|
|
// default
|
|
|
|
var newNodePos = {
|
|
|
|
x: pos.x - go.drag.offset.x,
|
|
|
|
y: pos.y - go.drag.offset.y
|
|
|
|
};
|
|
|
|
|
|
|
|
// bounds overrides
|
|
|
|
if(db.left !== undefined && newNodePos.x < db.left) {
|
|
|
|
newNodePos.x = db.left;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
2012-03-25 11:55:52 +08:00
|
|
|
if(db.right !== undefined && newNodePos.x > db.right) {
|
2012-03-25 11:52:17 +08:00
|
|
|
newNodePos.x = db.right;
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
2012-03-25 11:55:52 +08:00
|
|
|
if(db.top !== undefined && newNodePos.y < db.top) {
|
2012-03-25 11:52:17 +08:00
|
|
|
newNodePos.y = db.top;
|
|
|
|
}
|
2012-03-25 11:55:52 +08:00
|
|
|
if(db.bottom !== undefined && newNodePos.y > db.bottom) {
|
2012-03-25 11:52:17 +08:00
|
|
|
newNodePos.y = db.bottom;
|
|
|
|
}
|
|
|
|
|
|
|
|
// constraint overrides
|
|
|
|
if(dc === 'horizontal') {
|
|
|
|
newNodePos.y = node.y;
|
|
|
|
}
|
|
|
|
else if(dc === 'vertical') {
|
|
|
|
newNodePos.x = node.x;
|
|
|
|
}
|
|
|
|
|
2012-03-26 14:56:38 +08:00
|
|
|
/*
|
|
|
|
* save rotation and scale and then
|
|
|
|
* remove them from the transform
|
|
|
|
*/
|
2012-03-26 03:45:46 +08:00
|
|
|
var rot = node.rotation;
|
2012-03-26 14:56:38 +08:00
|
|
|
var scale = {
|
|
|
|
x: node.scale.x,
|
|
|
|
y: node.scale.y
|
|
|
|
};
|
2012-03-26 03:45:46 +08:00
|
|
|
node.rotation = 0;
|
2012-03-26 14:56:38 +08:00
|
|
|
node.scale = {
|
|
|
|
x: 1,
|
|
|
|
y: 1
|
|
|
|
};
|
2012-03-26 03:45:46 +08:00
|
|
|
|
2012-03-26 03:46:56 +08:00
|
|
|
// unravel transform
|
2012-03-25 11:52:17 +08:00
|
|
|
var it = node.getAbsoluteTransform();
|
|
|
|
it.invert();
|
|
|
|
it.translate(newNodePos.x, newNodePos.y);
|
|
|
|
|
|
|
|
node.x += it.getTranslation().x;
|
|
|
|
node.y += it.getTranslation().y;
|
|
|
|
|
2012-03-26 14:56:38 +08:00
|
|
|
// restore rotation and scale
|
2012-03-26 03:45:46 +08:00
|
|
|
node.rotate(rot);
|
2012-03-26 14:56:38 +08:00
|
|
|
node.scale = {
|
|
|
|
x: scale.x,
|
|
|
|
y: scale.y
|
|
|
|
};
|
2012-03-26 03:45:46 +08:00
|
|
|
|
2012-03-07 13:45:48 +08:00
|
|
|
go.drag.node.getLayer().draw();
|
|
|
|
|
|
|
|
if(!go.drag.moving) {
|
|
|
|
go.drag.moving = true;
|
|
|
|
// execute dragstart events if defined
|
2012-03-18 01:28:25 +08:00
|
|
|
go.drag.node._handleEvents('ondragstart', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
2012-03-26 03:45:46 +08:00
|
|
|
|
2012-03-07 13:45:48 +08:00
|
|
|
// execute user defined ondragmove if defined
|
2012-03-18 01:28:25 +08:00
|
|
|
go.drag.node._handleEvents('ondragmove', evt);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
}, false);
|
|
|
|
|
2012-03-23 04:47:37 +08:00
|
|
|
this.onContainer('mouseup touchend mouseout', function(evt) {
|
2012-03-07 13:45:48 +08:00
|
|
|
that._endDrag(evt);
|
|
|
|
});
|
2012-03-18 01:48:25 +08:00
|
|
|
},
|
|
|
|
/**
|
|
|
|
* build dom
|
|
|
|
*/
|
|
|
|
_buildDOM: function() {
|
|
|
|
// content
|
|
|
|
this.content.style.width = this.width + 'px';
|
|
|
|
this.content.style.height = this.height + 'px';
|
|
|
|
this.content.style.position = 'relative';
|
|
|
|
this.content.style.display = 'inline-block';
|
|
|
|
this.content.className = 'kineticjs-content';
|
|
|
|
this.container.appendChild(this.content);
|
|
|
|
|
|
|
|
// default layers
|
|
|
|
this.bufferLayer = new Kinetic.Layer();
|
2012-04-01 15:31:02 +08:00
|
|
|
this.pathLayer = new Kinetic.Layer();
|
2012-03-18 01:48:25 +08:00
|
|
|
|
|
|
|
// set parents
|
|
|
|
this.bufferLayer.parent = this;
|
2012-04-01 15:31:02 +08:00
|
|
|
this.pathLayer.parent = this;
|
2012-03-18 01:48:25 +08:00
|
|
|
|
|
|
|
// customize back stage context
|
2012-04-01 15:31:02 +08:00
|
|
|
this._modifyPathContext(this.pathLayer.context);
|
|
|
|
|
|
|
|
// hide canvases
|
2012-03-18 01:48:25 +08:00
|
|
|
this.bufferLayer.getCanvas().style.display = 'none';
|
2012-04-01 15:31:02 +08:00
|
|
|
this.pathLayer.getCanvas().style.display = 'none';
|
2012-03-18 01:48:25 +08:00
|
|
|
|
|
|
|
// add buffer layer
|
|
|
|
this.bufferLayer.canvas.width = this.width;
|
|
|
|
this.bufferLayer.canvas.height = this.height;
|
2012-04-01 15:31:02 +08:00
|
|
|
this.bufferLayer.canvas.className = 'kineticjs-buffer-layer';
|
2012-03-18 01:48:25 +08:00
|
|
|
this.content.appendChild(this.bufferLayer.canvas);
|
|
|
|
|
2012-04-01 15:31:02 +08:00
|
|
|
// add path layer
|
|
|
|
this.pathLayer.canvas.width = this.width;
|
|
|
|
this.pathLayer.canvas.height = this.height;
|
|
|
|
this.pathLayer.canvas.className = 'kineticjs-path-layer';
|
|
|
|
this.content.appendChild(this.pathLayer.canvas);
|
2012-03-07 13:45:48 +08:00
|
|
|
}
|
|
|
|
};
|
2012-03-23 04:47:37 +08:00
|
|
|
// Extend Container and Node
|
2012-03-11 08:52:16 +08:00
|
|
|
Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Container);
|
2012-03-23 04:47:37 +08:00
|
|
|
Kinetic.GlobalObject.extend(Kinetic.Stage, Kinetic.Node);
|