From 68f9688575ce9bada598ca5816daf7f5ab634b7e Mon Sep 17 00:00:00 2001 From: Eric Rowell Date: Fri, 23 Mar 2012 23:39:54 -0700 Subject: [PATCH] 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 --- dist/kinetic-core.js | 113 ++++++++++++++++++++++++++++++------ src/GlobalObject.js | 4 ++ src/Layer.js | 11 +++- src/Matrix.js | 41 +++++++++++-- src/Node.js | 33 +++++++++++ src/Shape.js | 7 ++- src/Stage.js | 15 +++-- tests/js/functionalTests.js | 19 +++--- 8 files changed, 197 insertions(+), 46 deletions(-) diff --git a/dist/kinetic-core.js b/dist/kinetic-core.js index a887fabd..09263c43 100644 --- a/dist/kinetic-core.js +++ b/dist/kinetic-core.js @@ -3,7 +3,7 @@ * http://www.kineticjs.com/ * Copyright 2012, Eric Rowell * Licensed under the MIT or GPL Version 2 licenses. - * Date: Mar 22 2012 + * Date: Mar 23 2012 * * Copyright (C) 2011 - 2012 by Eric Rowell * @@ -52,6 +52,10 @@ Kinetic.GlobalObject = { offset: { x: 0, y: 0 + }, + start: { + x: 0, + y: 0 } }, extend: function(obj1, obj2) { @@ -754,6 +758,35 @@ Kinetic.Node.prototype = { getDragBounds: function() { 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() { var m = new Kinetic.Matrix(); @@ -783,9 +816,13 @@ Kinetic.Node.prototype = { var pos = stage.getUserPosition(); if(pos) { + var m = that.getMatrix().getTranslation(); + var am = that.getAbsoluteMatrix().getTranslation(); go.drag.node = that; go.drag.offset.x = pos.x - that.x; 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 * a DOM element */ - if(typeof config.container === 'string') { + if( typeof config.container === 'string') { config.container = document.getElementById(config.container); } @@ -1536,18 +1573,21 @@ Kinetic.Stage.prototype = { var node = go.drag.node; if(node) { var pos = that.getUserPosition(); - var ds = node.dragConstraint; + var dc = node.dragConstraint; 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; 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; 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(); @@ -1634,13 +1674,17 @@ Kinetic.Layer = function(config) { */ Kinetic.Layer.prototype = { /** - * public draw children + * draw children nodes. this includes any groups + * or shapes */ draw: function() { 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() { var context = this.getContext(); @@ -1660,7 +1704,8 @@ Kinetic.Layer.prototype = { return this.context; }, /** - * add node to layer + * add a node to the layer. New nodes are always + * placed at the top. * @param {Node} node */ add: function(child) { @@ -1859,12 +1904,13 @@ Kinetic.Shape.prototype = { family.unshift(parent); parent = parent.parent; } - + context.save(); for(var n = 0; n < family.length; n++) { var node = family[n]; - var m = node.getMatrix(); - m.transformContext(context); + var m = node.getMatrix().toArray(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + if(node.getAbsoluteAlpha() !== 1) { 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 * project, KineticJS-Ext by Wappworks, which is based on Simon's Transform -* class. KineticJS has slightly modified the original class and added new methods -* specific for canvas. +* class. */ /** @@ -2543,11 +2588,41 @@ Kinetic.Matrix.prototype = { this.m[3] = m22; }, /** - * transform canvas context + * Returns the translation + * @returns {Object} 2D point(x, y) */ - transformContext: function(context) { - var m = this.m; - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + getTranslation: function() { + return { + 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; } }; diff --git a/src/GlobalObject.js b/src/GlobalObject.js index 9d3a15d2..dfe17083 100644 --- a/src/GlobalObject.js +++ b/src/GlobalObject.js @@ -24,6 +24,10 @@ Kinetic.GlobalObject = { offset: { x: 0, y: 0 + }, + start: { + x: 0, + y: 0 } }, extend: function(obj1, obj2) { diff --git a/src/Layer.js b/src/Layer.js index 063a1662..12b0277a 100644 --- a/src/Layer.js +++ b/src/Layer.js @@ -26,13 +26,17 @@ Kinetic.Layer = function(config) { */ Kinetic.Layer.prototype = { /** - * public draw children + * draw children nodes. this includes any groups + * or shapes */ draw: function() { 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() { var context = this.getContext(); @@ -52,7 +56,8 @@ Kinetic.Layer.prototype = { return this.context; }, /** - * add node to layer + * add a node to the layer. New nodes are always + * placed at the top. * @param {Node} node */ add: function(child) { diff --git a/src/Matrix.js b/src/Matrix.js index 1fb50e9a..facbd38a 100644 --- a/src/Matrix.js +++ b/src/Matrix.js @@ -11,8 +11,7 @@ /* * 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 -* class. KineticJS has slightly modified the original class and added new methods -* specific for canvas. +* class. */ /** @@ -60,10 +59,40 @@ Kinetic.Matrix.prototype = { this.m[3] = m22; }, /** - * transform canvas context + * Returns the translation + * @returns {Object} 2D point(x, y) */ - transformContext: function(context) { - var m = this.m; - context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + getTranslation: function() { + return { + 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; } }; diff --git a/src/Node.js b/src/Node.js index d326a193..88e9fb5b 100644 --- a/src/Node.js +++ b/src/Node.js @@ -507,6 +507,35 @@ Kinetic.Node.prototype = { getDragBounds: function() { 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() { var m = new Kinetic.Matrix(); @@ -536,9 +565,13 @@ Kinetic.Node.prototype = { var pos = stage.getUserPosition(); if(pos) { + var m = that.getMatrix().getTranslation(); + var am = that.getAbsoluteMatrix().getTranslation(); go.drag.node = that; go.drag.offset.x = pos.x - that.x; go.drag.offset.y = pos.y - that.y; + go.drag.start.x = m.x - am.x; + go.drag.start.y = m.y - am.y; } }); }, diff --git a/src/Shape.js b/src/Shape.js index 7504a163..0b550cdd 100644 --- a/src/Shape.js +++ b/src/Shape.js @@ -120,12 +120,13 @@ Kinetic.Shape.prototype = { family.unshift(parent); parent = parent.parent; } - + context.save(); for(var n = 0; n < family.length; n++) { var node = family[n]; - var m = node.getMatrix(); - m.transformContext(context); + var m = node.getMatrix().toArray(); + context.transform(m[0], m[1], m[2], m[3], m[4], m[5]); + if(node.getAbsoluteAlpha() !== 1) { context.globalAlpha = node.getAbsoluteAlpha(); } diff --git a/src/Stage.js b/src/Stage.js index 055d2f8e..1b8ae494 100644 --- a/src/Stage.js +++ b/src/Stage.js @@ -16,7 +16,7 @@ Kinetic.Stage = function(config) { * if container is a string, assume it's an id for * a DOM element */ - if(typeof config.container === 'string') { + if( typeof config.container === 'string') { config.container = document.getElementById(config.container); } @@ -587,18 +587,21 @@ Kinetic.Stage.prototype = { var node = go.drag.node; if(node) { var pos = that.getUserPosition(); - var ds = node.dragConstraint; + var dc = node.dragConstraint; 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; 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; 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(); diff --git a/tests/js/functionalTests.js b/tests/js/functionalTests.js index 5573581e..a2623643 100644 --- a/tests/js/functionalTests.js +++ b/tests/js/functionalTests.js @@ -979,27 +979,28 @@ Test.prototype.tests = { 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({ container: containerId, width: 578, height: 200 }); var layer = new Kinetic.Layer(); - var circle = new Kinetic.Circle({ - x: stage.width / 2, - y: stage.height / 2, - radius: 70, + var rect = new Kinetic.Rect({ + x: 200, + y: 80, + width: 100, + height: 50, fill: 'red', stroke: 'black', - strokeWidth: 4 + strokeWidth: 4, + draggable: true }); - circle.draggable(true); - - layer.add(circle); + layer.add(rect); stage.add(layer); + stage.rotateDeg(20); stage.setScale(0.5); stage.draw();