finished matrix integration. the stage, layers, groups, and nodes can be transformed in anyway, and drag and drop on any of these nodes now works great regardless of parent node transforms

This commit is contained in:
Eric Rowell 2012-03-23 23:39:54 -07:00
parent 7ced50f694
commit 68f9688575
8 changed files with 197 additions and 46 deletions

111
dist/kinetic-core.js vendored
View File

@ -3,7 +3,7 @@
* http://www.kineticjs.com/ * http://www.kineticjs.com/
* Copyright 2012, Eric Rowell * Copyright 2012, Eric Rowell
* Licensed under the MIT or GPL Version 2 licenses. * Licensed under the MIT or GPL Version 2 licenses.
* Date: Mar 22 2012 * Date: Mar 23 2012
* *
* Copyright (C) 2011 - 2012 by Eric Rowell * Copyright (C) 2011 - 2012 by Eric Rowell
* *
@ -52,6 +52,10 @@ Kinetic.GlobalObject = {
offset: { offset: {
x: 0, x: 0,
y: 0 y: 0
},
start: {
x: 0,
y: 0
} }
}, },
extend: function(obj1, obj2) { extend: function(obj1, obj2) {
@ -754,6 +758,35 @@ Kinetic.Node.prototype = {
getDragBounds: function() { getDragBounds: function() {
return this.dragBounds; return this.dragBounds;
}, },
/**
* get matrix transform of the node while taking into
* account the matrix transforms of its parents
*/
getAbsoluteMatrix: function() {
// absolute matrix
var am = new Kinetic.Matrix();
var family = [];
var parent = this.parent;
family.unshift(this);
while(parent) {
family.unshift(parent);
parent = parent.parent;
}
for(var n = 0; n < family.length; n++) {
var node = family[n];
var m = node.getMatrix();
am.multiply(m);
}
return am;
},
/**
* get matrix transform of the node while not taking
* into account the matrix transforms of its parents
*/
getMatrix: function() { getMatrix: function() {
var m = new Kinetic.Matrix(); var m = new Kinetic.Matrix();
@ -783,9 +816,13 @@ Kinetic.Node.prototype = {
var pos = stage.getUserPosition(); var pos = stage.getUserPosition();
if(pos) { if(pos) {
var m = that.getMatrix().getTranslation();
var am = that.getAbsoluteMatrix().getTranslation();
go.drag.node = that; go.drag.node = that;
go.drag.offset.x = pos.x - that.x; go.drag.offset.x = pos.x - that.x;
go.drag.offset.y = pos.y - that.y; go.drag.offset.y = pos.y - that.y;
go.drag.start.x = m.x - am.x;
go.drag.start.y = m.y - am.y;
} }
}); });
}, },
@ -965,7 +1002,7 @@ Kinetic.Stage = function(config) {
* if container is a string, assume it's an id for * if container is a string, assume it's an id for
* a DOM element * a DOM element
*/ */
if(typeof config.container === 'string') { if( typeof config.container === 'string') {
config.container = document.getElementById(config.container); config.container = document.getElementById(config.container);
} }
@ -1536,18 +1573,21 @@ Kinetic.Stage.prototype = {
var node = go.drag.node; var node = go.drag.node;
if(node) { if(node) {
var pos = that.getUserPosition(); var pos = that.getUserPosition();
var ds = node.dragConstraint; var dc = node.dragConstraint;
var db = node.dragBounds; var db = node.dragBounds;
if(ds === 'none' || ds === 'horizontal') { var m = node.getMatrix().getTranslation();
var am = node.getAbsoluteMatrix().getTranslation();
if(dc === 'none' || dc === 'horizontal') {
var newX = pos.x - go.drag.offset.x; var newX = pos.x - go.drag.offset.x;
if((db.left === undefined || db.left < newX) && (db.right === undefined || db.right > newX)) { if((db.left === undefined || db.left < newX) && (db.right === undefined || db.right > newX)) {
node.x = newX; node.x = newX + m.x - (am.x + go.drag.start.x);
} }
} }
if(ds === 'none' || ds === 'vertical') { if(dc === 'none' || dc === 'vertical') {
var newY = pos.y - go.drag.offset.y; var newY = pos.y - go.drag.offset.y;
if((db.top === undefined || db.top < newY) && (db.bottom === undefined || db.bottom > newY)) { if((db.top === undefined || db.top < newY) && (db.bottom === undefined || db.bottom > newY)) {
node.y = newY; node.y = newY + m.y - (am.y + go.drag.start.y);
} }
} }
go.drag.node.getLayer().draw(); go.drag.node.getLayer().draw();
@ -1634,13 +1674,17 @@ Kinetic.Layer = function(config) {
*/ */
Kinetic.Layer.prototype = { Kinetic.Layer.prototype = {
/** /**
* public draw children * draw children nodes. this includes any groups
* or shapes
*/ */
draw: function() { draw: function() {
this._draw(); this._draw();
}, },
/** /**
* clear layer * clears the canvas context tied to the layer. Clearing
* a layer does not remove its children. The nodes within
* the layer will be redrawn whenever the .draw() method
* is used again.
*/ */
clear: function() { clear: function() {
var context = this.getContext(); var context = this.getContext();
@ -1660,7 +1704,8 @@ Kinetic.Layer.prototype = {
return this.context; return this.context;
}, },
/** /**
* add node to layer * add a node to the layer. New nodes are always
* placed at the top.
* @param {Node} node * @param {Node} node
*/ */
add: function(child) { add: function(child) {
@ -1863,8 +1908,9 @@ Kinetic.Shape.prototype = {
context.save(); context.save();
for(var n = 0; n < family.length; n++) { for(var n = 0; n < family.length; n++) {
var node = family[n]; var node = family[n];
var m = node.getMatrix(); var m = node.getMatrix().toArray();
m.transformContext(context); context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
if(node.getAbsoluteAlpha() !== 1) { if(node.getAbsoluteAlpha() !== 1) {
context.globalAlpha = node.getAbsoluteAlpha(); context.globalAlpha = node.getAbsoluteAlpha();
} }
@ -2494,8 +2540,7 @@ Kinetic.GlobalObject.extend(Kinetic.Text, Kinetic.Shape);
/* /*
* The usage of this class was inspired by some of the work done by a forked * The usage of this class was inspired by some of the work done by a forked
* project, KineticJS-Ext by Wappworks, which is based on Simon's Transform * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform
* class. KineticJS has slightly modified the original class and added new methods * class.
* specific for canvas.
*/ */
/** /**
@ -2543,11 +2588,41 @@ Kinetic.Matrix.prototype = {
this.m[3] = m22; this.m[3] = m22;
}, },
/** /**
* transform canvas context * Returns the translation
* @returns {Object} 2D point(x, y)
*/ */
transformContext: function(context) { getTranslation: function() {
var m = this.m; return {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); x: this.m[4],
y: this.m[5]
};
},
/**
* Transform multiplication
* @param {Kinetic.Matrix} matrix
*/
multiply: function(matrix) {
var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
this.m[4] = dx;
this.m[5] = dy;
},
/**
* return matrix as array
*/
toArray: function() {
return this.m;
} }
}; };

View File

@ -24,6 +24,10 @@ Kinetic.GlobalObject = {
offset: { offset: {
x: 0, x: 0,
y: 0 y: 0
},
start: {
x: 0,
y: 0
} }
}, },
extend: function(obj1, obj2) { extend: function(obj1, obj2) {

View File

@ -26,13 +26,17 @@ Kinetic.Layer = function(config) {
*/ */
Kinetic.Layer.prototype = { Kinetic.Layer.prototype = {
/** /**
* public draw children * draw children nodes. this includes any groups
* or shapes
*/ */
draw: function() { draw: function() {
this._draw(); this._draw();
}, },
/** /**
* clear layer * clears the canvas context tied to the layer. Clearing
* a layer does not remove its children. The nodes within
* the layer will be redrawn whenever the .draw() method
* is used again.
*/ */
clear: function() { clear: function() {
var context = this.getContext(); var context = this.getContext();
@ -52,7 +56,8 @@ Kinetic.Layer.prototype = {
return this.context; return this.context;
}, },
/** /**
* add node to layer * add a node to the layer. New nodes are always
* placed at the top.
* @param {Node} node * @param {Node} node
*/ */
add: function(child) { add: function(child) {

View File

@ -11,8 +11,7 @@
/* /*
* The usage of this class was inspired by some of the work done by a forked * The usage of this class was inspired by some of the work done by a forked
* project, KineticJS-Ext by Wappworks, which is based on Simon's Transform * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform
* class. KineticJS has slightly modified the original class and added new methods * class.
* specific for canvas.
*/ */
/** /**
@ -60,10 +59,40 @@ Kinetic.Matrix.prototype = {
this.m[3] = m22; this.m[3] = m22;
}, },
/** /**
* transform canvas context * Returns the translation
* @returns {Object} 2D point(x, y)
*/ */
transformContext: function(context) { getTranslation: function() {
var m = this.m; return {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); x: this.m[4],
y: this.m[5]
};
},
/**
* Transform multiplication
* @param {Kinetic.Matrix} matrix
*/
multiply: function(matrix) {
var m11 = this.m[0] * matrix.m[0] + this.m[2] * matrix.m[1];
var m12 = this.m[1] * matrix.m[0] + this.m[3] * matrix.m[1];
var m21 = this.m[0] * matrix.m[2] + this.m[2] * matrix.m[3];
var m22 = this.m[1] * matrix.m[2] + this.m[3] * matrix.m[3];
var dx = this.m[0] * matrix.m[4] + this.m[2] * matrix.m[5] + this.m[4];
var dy = this.m[1] * matrix.m[4] + this.m[3] * matrix.m[5] + this.m[5];
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
this.m[4] = dx;
this.m[5] = dy;
},
/**
* return matrix as array
*/
toArray: function() {
return this.m;
} }
}; };

View File

@ -507,6 +507,35 @@ Kinetic.Node.prototype = {
getDragBounds: function() { getDragBounds: function() {
return this.dragBounds; return this.dragBounds;
}, },
/**
* get matrix transform of the node while taking into
* account the matrix transforms of its parents
*/
getAbsoluteMatrix: function() {
// absolute matrix
var am = new Kinetic.Matrix();
var family = [];
var parent = this.parent;
family.unshift(this);
while(parent) {
family.unshift(parent);
parent = parent.parent;
}
for(var n = 0; n < family.length; n++) {
var node = family[n];
var m = node.getMatrix();
am.multiply(m);
}
return am;
},
/**
* get matrix transform of the node while not taking
* into account the matrix transforms of its parents
*/
getMatrix: function() { getMatrix: function() {
var m = new Kinetic.Matrix(); var m = new Kinetic.Matrix();
@ -536,9 +565,13 @@ Kinetic.Node.prototype = {
var pos = stage.getUserPosition(); var pos = stage.getUserPosition();
if(pos) { if(pos) {
var m = that.getMatrix().getTranslation();
var am = that.getAbsoluteMatrix().getTranslation();
go.drag.node = that; go.drag.node = that;
go.drag.offset.x = pos.x - that.x; go.drag.offset.x = pos.x - that.x;
go.drag.offset.y = pos.y - that.y; go.drag.offset.y = pos.y - that.y;
go.drag.start.x = m.x - am.x;
go.drag.start.y = m.y - am.y;
} }
}); });
}, },

View File

@ -124,8 +124,9 @@ Kinetic.Shape.prototype = {
context.save(); context.save();
for(var n = 0; n < family.length; n++) { for(var n = 0; n < family.length; n++) {
var node = family[n]; var node = family[n];
var m = node.getMatrix(); var m = node.getMatrix().toArray();
m.transformContext(context); context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
if(node.getAbsoluteAlpha() !== 1) { if(node.getAbsoluteAlpha() !== 1) {
context.globalAlpha = node.getAbsoluteAlpha(); context.globalAlpha = node.getAbsoluteAlpha();
} }

View File

@ -16,7 +16,7 @@ Kinetic.Stage = function(config) {
* if container is a string, assume it's an id for * if container is a string, assume it's an id for
* a DOM element * a DOM element
*/ */
if(typeof config.container === 'string') { if( typeof config.container === 'string') {
config.container = document.getElementById(config.container); config.container = document.getElementById(config.container);
} }
@ -587,18 +587,21 @@ Kinetic.Stage.prototype = {
var node = go.drag.node; var node = go.drag.node;
if(node) { if(node) {
var pos = that.getUserPosition(); var pos = that.getUserPosition();
var ds = node.dragConstraint; var dc = node.dragConstraint;
var db = node.dragBounds; var db = node.dragBounds;
if(ds === 'none' || ds === 'horizontal') { var m = node.getMatrix().getTranslation();
var am = node.getAbsoluteMatrix().getTranslation();
if(dc === 'none' || dc === 'horizontal') {
var newX = pos.x - go.drag.offset.x; var newX = pos.x - go.drag.offset.x;
if((db.left === undefined || db.left < newX) && (db.right === undefined || db.right > newX)) { if((db.left === undefined || db.left < newX) && (db.right === undefined || db.right > newX)) {
node.x = newX; node.x = newX + m.x - (am.x + go.drag.start.x);
} }
} }
if(ds === 'none' || ds === 'vertical') { if(dc === 'none' || dc === 'vertical') {
var newY = pos.y - go.drag.offset.y; var newY = pos.y - go.drag.offset.y;
if((db.top === undefined || db.top < newY) && (db.bottom === undefined || db.bottom > newY)) { if((db.top === undefined || db.top < newY) && (db.bottom === undefined || db.bottom > newY)) {
node.y = newY; node.y = newY + m.y - (am.y + go.drag.start.y);
} }
} }
go.drag.node.getLayer().draw(); go.drag.node.getLayer().draw();

View File

@ -979,27 +979,28 @@ Test.prototype.tests = {
circle.draggable(false); circle.draggable(false);
}, },
'DRAG AND DROP - scale stage after add layer then drag and drop shape': function(containerId) { 'DRAG AND DROP - scale and rotate stage after add layer then drag and drop shape': function(containerId) {
var stage = new Kinetic.Stage({ var stage = new Kinetic.Stage({
container: containerId, container: containerId,
width: 578, width: 578,
height: 200 height: 200
}); });
var layer = new Kinetic.Layer(); var layer = new Kinetic.Layer();
var circle = new Kinetic.Circle({ var rect = new Kinetic.Rect({
x: stage.width / 2, x: 200,
y: stage.height / 2, y: 80,
radius: 70, width: 100,
height: 50,
fill: 'red', fill: 'red',
stroke: 'black', stroke: 'black',
strokeWidth: 4 strokeWidth: 4,
draggable: true
}); });
circle.draggable(true); layer.add(rect);
layer.add(circle);
stage.add(layer); stage.add(layer);
stage.rotateDeg(20);
stage.setScale(0.5); stage.setScale(0.5);
stage.draw(); stage.draw();