rewrote dynamic drag and drop layer because the old implementation had too many problems. This resolves all of the dynamic drag and drop issues in one go. draw() method can now be applied to any node, not just the Stage and Layers. drag events now bubble

This commit is contained in:
Eric Rowell
2013-03-22 00:46:41 -07:00
parent 0dbda82886
commit 0c80f6e223
8 changed files with 147 additions and 91 deletions

View File

@@ -11,20 +11,28 @@
* @param {Number} width * @param {Number} width
* @param {Number} height * @param {Number} height
*/ */
Kinetic.Canvas = function(width, height, pixelRatio) { Kinetic.Canvas = function(config) {
this.pixelRatio = pixelRatio || _pixelRatio; this.init(config);
this.width = width;
this.height = height;
this.element = document.createElement('canvas');
this.element.style.padding = 0;
this.element.style.margin = 0;
this.element.style.border = 0;
this.element.style.background = 'transparent';
this.context = this.element.getContext('2d');
this.setSize(width || 0, height || 0);
}; };
Kinetic.Canvas.prototype = { Kinetic.Canvas.prototype = {
init: function(config) {
var config = config || {},
width = config.width || 0,
height = config.height || 0,
pixelRatio = config.pixelRatio || _pixelRatio;
this.pixelRatio = pixelRatio;
this.width = width;
this.height = height;
this.element = document.createElement('canvas');
this.element.style.padding = 0;
this.element.style.margin = 0;
this.element.style.border = 0;
this.element.style.background = 'transparent';
this.context = this.element.getContext('2d');
this.setSize(width, height);
},
/** /**
* clear canvas * clear canvas
* @name clear * @name clear

View File

@@ -196,27 +196,35 @@
children[n].index = n; children[n].index = n;
} }
}, },
/*
* draw both scene and hit graphs
*/
draw: function() {
this.drawScene();
this.drawHit();
},
drawScene: function(canvas) { drawScene: function(canvas) {
var clip = !!this.getClipFunc() && canvas; var layer = this.getLayer(),
clip = !!this.getClipFunc(),
stage = this.getStage(),
children, len;
if (clip) { if (!canvas && layer) {
canvas._clip(this); canvas = layer.getCanvas();
} }
if(layer && layer.getClearBeforeDraw()) {
canvas.clear();
}
if(this.isVisible()) { if(this.isVisible()) {
var children = this.children, len = children.length; if (clip) {
canvas._clip(this);
}
children = this.children,
len = children.length;
for(var n = 0; n < len; n++) { for(var n = 0; n < len; n++) {
children[n].drawScene(canvas); children[n].drawScene(canvas);
} }
}
if (clip) { if (clip) {
canvas.getContext().restore(); canvas.getContext().restore();
}
} }
}, },
drawHit: function() { drawHit: function() {

View File

@@ -11,25 +11,6 @@
Kinetic.getNodeDragging = function() { Kinetic.getNodeDragging = function() {
return Kinetic.DD.node; return Kinetic.DD.node;
}; };
Kinetic.DD._setupDragLayerAndGetContainer = function(no) {
var stage = no.getStage(), nodeType = no.nodeType, lastContainer, group;
// re-construct node tree
no._eachAncestorReverse(function(node) {
if(node.nodeType === 'Layer') {
stage.dragLayer.setAttrs(node.getAttrs());
lastContainer = stage.dragLayer;
stage.add(stage.dragLayer);
}
else if(node.nodeType === 'Group') {
group = new Kinetic.Group(node.getAttrs());
lastContainer.add(group);
lastContainer = group;
}
});
return lastContainer;
};
Kinetic.DD._initDragLayer = function(stage) { Kinetic.DD._initDragLayer = function(stage) {
stage.dragLayer = new Kinetic.Layer(); stage.dragLayer = new Kinetic.Layer();
stage.dragLayer.getCanvas().getElement().className = 'kinetic-drag-and-drop-layer'; stage.dragLayer.getCanvas().getElement().className = 'kinetic-drag-and-drop-layer';
@@ -75,12 +56,11 @@
} }
// else if group, shape, or layer // else if group, shape, or layer
else { else {
if((nodeType === 'Group' || nodeType === 'Shape') && node.getDragOnTop() && dd.prevParent) { if((nodeType === 'Group' || nodeType === 'Shape') && node.getDragOnTop()) {
node.moveTo(dd.prevParent);
node.getStage().dragLayer.remove(); node.getStage().dragLayer.remove();
dd.prevParent = null;
} }
node.moveToTop();
node.getLayer().draw(); node.getLayer().draw();
} }
@@ -97,7 +77,11 @@
} }
}; };
Kinetic.Node.prototype._startDrag = function(evt) { Kinetic.Node.prototype._startDrag = function(evt) {
var dd = Kinetic.DD, that = this, stage = this.getStage(), pos = stage.getUserPosition(); var dd = Kinetic.DD,
that = this,
stage = this.getStage(),
layer = this.getLayer(),
pos = stage.getUserPosition();
if(pos) { if(pos) {
var m = this.getTransform().getTranslation(), ap = this.getAbsolutePosition(), nodeType = this.nodeType, container; var m = this.getTransform().getTranslation(), ap = this.getAbsolutePosition(), nodeType = this.nodeType, container;
@@ -105,34 +89,34 @@
dd.node = this; dd.node = this;
dd.offset.x = pos.x - ap.x; dd.offset.x = pos.x - ap.x;
dd.offset.y = pos.y - ap.y; dd.offset.y = pos.y - ap.y;
dd.anim.node = this;
// Stage and Layer node types // Stage and Layer node types
if(nodeType === 'Stage' || nodeType === 'Layer') { if(nodeType === 'Stage' || nodeType === 'Layer') {
dd.anim.node = this;
dd.anim.start(); dd.anim.start();
} }
// Group or Shape node types // Group or Shape node types
else { else {
if(this.getDragOnTop()) { if(this.getDragOnTop()) {
container = dd._setupDragLayerAndGetContainer(this);
dd.anim.node = stage.dragLayer;
dd.prevParent = this.getParent();
// WARNING: it's important to delay the moveTo operation, // WARNING: it's important to delay the moveTo operation,
// layer redraws, and anim.start() until after the method execution // layer redraws, and anim.start() until after the method execution
// has completed or else there will be a flicker on mobile devices // has completed or else there will be a flicker on mobile devices
// due to the time it takes to append the dd canvas to the DOM // due to the time it takes to append the dd canvas to the DOM
setTimeout(function() { //setTimeout(function() {
if(dd.node) { //if(dd.node) {
that.moveTo(container); // clear shape from layer canvas
dd.prevParent.getLayer().draw(); that.setVisible(false);
stage.dragLayer.draw(); layer.draw();
that.setVisible(true);
stage.add(stage.dragLayer);
dd.anim.start(); dd.anim.start();
} //}
}, 0); //}, 0);
} }
else { else {
dd.anim.node = this.getLayer();
dd.anim.start(); dd.anim.start();
} }
} }

View File

@@ -34,8 +34,6 @@
* @methodOf Kinetic.Layer.prototype * @methodOf Kinetic.Layer.prototype
*/ */
draw: function() { draw: function() {
var context = this.getContext();
// before draw handler // before draw handler
if(this.beforeDrawFunc !== undefined) { if(this.beforeDrawFunc !== undefined) {
this.beforeDrawFunc.call(this); this.beforeDrawFunc.call(this);
@@ -116,7 +114,8 @@
* @methodOf Kinetic.Layer.prototype * @methodOf Kinetic.Layer.prototype
*/ */
getCanvas: function() { getCanvas: function() {
return this.canvas; var stage = this.getStage();
return (stage && stage._isTempDDLayerActive()) ? stage.dragLayer.canvas : this.canvas;
}, },
/** /**
* get layer canvas context * get layer canvas context
@@ -124,7 +123,7 @@
* @methodOf Kinetic.Layer.prototype * @methodOf Kinetic.Layer.prototype
*/ */
getContext: function() { getContext: function() {
return this.canvas.context; return this.getCanvas().getContext();
}, },
/** /**
* clear canvas tied to the layer * clear canvas tied to the layer
@@ -138,11 +137,11 @@
setVisible: function(visible) { setVisible: function(visible) {
Kinetic.Node.prototype.setVisible.call(this, visible); Kinetic.Node.prototype.setVisible.call(this, visible);
if(visible) { if(visible) {
this.canvas.element.style.display = 'block'; this.getCanvas().element.style.display = 'block';
this.hitCanvas.element.style.display = 'block'; this.hitCanvas.element.style.display = 'block';
} }
else { else {
this.canvas.element.style.display = 'none'; this.getCanvas().element.style.display = 'none';
this.hitCanvas.element.style.display = 'none'; this.hitCanvas.element.style.display = 'none';
} }
}, },
@@ -150,13 +149,13 @@
Kinetic.Node.prototype.setZIndex.call(this, index); Kinetic.Node.prototype.setZIndex.call(this, index);
var stage = this.getStage(); var stage = this.getStage();
if(stage) { if(stage) {
stage.content.removeChild(this.canvas.element); stage.content.removeChild(this.getCanvas().element);
if(index < stage.getChildren().length - 1) { if(index < stage.getChildren().length - 1) {
stage.content.insertBefore(this.canvas.element, stage.getChildren()[index + 1].canvas.element); stage.content.insertBefore(this.getCanvas().element, stage.getChildren()[index + 1].getCanvas().element);
} }
else { else {
stage.content.appendChild(this.canvas.element); stage.content.appendChild(this.getCanvas().element);
} }
} }
}, },
@@ -164,21 +163,21 @@
Kinetic.Node.prototype.moveToTop.call(this); Kinetic.Node.prototype.moveToTop.call(this);
var stage = this.getStage(); var stage = this.getStage();
if(stage) { if(stage) {
stage.content.removeChild(this.canvas.element); stage.content.removeChild(this.getCanvas().element);
stage.content.appendChild(this.canvas.element); stage.content.appendChild(this.getCanvas().element);
} }
}, },
moveUp: function() { moveUp: function() {
if(Kinetic.Node.prototype.moveUp.call(this)) { if(Kinetic.Node.prototype.moveUp.call(this)) {
var stage = this.getStage(); var stage = this.getStage();
if(stage) { if(stage) {
stage.content.removeChild(this.canvas.element); stage.content.removeChild(this.getCanvas().element);
if(this.index < stage.getChildren().length - 1) { if(this.index < stage.getChildren().length - 1) {
stage.content.insertBefore(this.canvas.element, stage.getChildren()[this.index + 1].canvas.element); stage.content.insertBefore(this.getCanvas().element, stage.getChildren()[this.index + 1].getCanvas().element);
} }
else { else {
stage.content.appendChild(this.canvas.element); stage.content.appendChild(this.getCanvas().element);
} }
} }
} }
@@ -188,8 +187,8 @@
var stage = this.getStage(); var stage = this.getStage();
if(stage) { if(stage) {
var children = stage.getChildren(); var children = stage.getChildren();
stage.content.removeChild(this.canvas.element); stage.content.removeChild(this.getCanvas().element);
stage.content.insertBefore(this.canvas.element, children[this.index + 1].canvas.element); stage.content.insertBefore(this.getCanvas().element, children[this.index + 1].getCanvas().element);
} }
} }
}, },
@@ -198,8 +197,8 @@
var stage = this.getStage(); var stage = this.getStage();
if(stage) { if(stage) {
var children = stage.getChildren(); var children = stage.getChildren();
stage.content.removeChild(this.canvas.element); stage.content.removeChild(this.getCanvas().element);
stage.content.insertBefore(this.canvas.element, children[1].canvas.element); stage.content.insertBefore(this.getCanvas().element, children[1].getCanvas().element);
} }
} }
}, },
@@ -210,7 +209,7 @@
* remove layer from stage * remove layer from stage
*/ */
remove: function() { remove: function() {
var stage = this.getStage(), canvas = this.canvas, element = canvas.element; var stage = this.getStage(), canvas = this.getCanvas(), element = canvas.element;
Kinetic.Node.prototype.remove.call(this); Kinetic.Node.prototype.remove.call(this);
if(stage && canvas && Kinetic.Type._isInDocument(element)) { if(stage && canvas && Kinetic.Type._isInDocument(element)) {

View File

@@ -696,7 +696,11 @@
//if width and height are defined, create new canvas to draw on, else reuse stage buffer canvas //if width and height are defined, create new canvas to draw on, else reuse stage buffer canvas
if(config.width && config.height) { if(config.width && config.height) {
canvas = new Kinetic.SceneCanvas(config.width, config.height, 1); canvas = new Kinetic.SceneCanvas({
width: config.width,
height: config.height,
pixelRatio: 1
});
} }
else { else {
canvas = this.getStage().bufferCanvas; canvas = this.getStage().bufferCanvas;
@@ -910,6 +914,22 @@
events[i].handler.apply(this, [evt]); events[i].handler.apply(this, [evt]);
} }
} }
},
/*
* draw both scene and hit graphs.
* @name draw
* @methodOf Kinetic.Node.prototype
* the scene renderer
*/
draw: function() {
var layer = this.getLayer();
if(layer && layer.getClearBeforeDraw()) {
layer.getCanvas().clear();
}
this.drawScene();
this.drawHit();
} }
}; };

View File

@@ -168,7 +168,10 @@
delete Kinetic.Global.shapes[this.colorKey]; delete Kinetic.Global.shapes[this.colorKey];
}, },
drawScene: function(canvas) { drawScene: function(canvas) {
var attrs = this.attrs, drawFunc = attrs.drawFunc, canvas = canvas || this.getLayer().getCanvas(), context = canvas.getContext(); var attrs = this.getAttrs(),
drawFunc = attrs.drawFunc,
canvas = canvas || this.getLayer().getCanvas(),
context = canvas.getContext();
if(drawFunc && this.isVisible()) { if(drawFunc && this.isVisible()) {
context.save(); context.save();
@@ -180,7 +183,10 @@
} }
}, },
drawHit: function() { drawHit: function() {
var attrs = this.attrs, drawFunc = attrs.drawHitFunc || attrs.drawFunc, canvas = this.getLayer().hitCanvas, context = canvas.getContext(); var attrs = this.getAttrs(),
drawFunc = attrs.drawHitFunc || attrs.drawFunc,
canvas = this.getLayer().hitCanvas,
context = canvas.getContext();
if(drawFunc && this.isVisible() && this.isListening()) { if(drawFunc && this.isVisible() && this.isListening()) {
context.save(); context.save();

View File

@@ -163,7 +163,10 @@
quality = config.quality || null, quality = config.quality || null,
x = config.x || 0, x = config.x || 0,
y = config.y || 0, y = config.y || 0,
canvas = new Kinetic.SceneCanvas(config.width || this.getWidth(), config.height || this.getHeight()), canvas = new Kinetic.SceneCanvas({
width: config.width || this.getWidth(),
height: config.height || this.getHeight()
}),
context = canvas.getContext(), context = canvas.getContext(),
layers = this.children; layers = this.children;
@@ -296,6 +299,16 @@
getDragLayer: function() { getDragLayer: function() {
return this.dragLayer; return this.dragLayer;
}, },
getParent: function() {
return null;
},
getLayer: function() {
return null;
},
_isTempDDLayerActive: function() {
var dragLayer = this.dragLayer;
return dragLayer && dragLayer.getStage();
},
_setUserPosition: function(evt) { _setUserPosition: function(evt) {
if(!evt) { if(!evt) {
evt = window.event; evt = window.event;

View File

@@ -46,26 +46,40 @@ Test.Modules.DD = {
// which can't be simulated. call _endDrag manually // which can't be simulated. call _endDrag manually
Kinetic.DD._endDrag(); Kinetic.DD._endDrag();
}, },
'*test dragstart, dragmove, dragend': function(containerId) { 'test dragstart, dragmove, dragend': function(containerId) {
var stage = new Kinetic.Stage({ var stage = new Kinetic.Stage({
container: containerId, container: containerId,
width: 578, width: 578,
height: 200, height: 200
throttle: 999
}); });
var layer = new Kinetic.Layer(); var layer = new Kinetic.Layer();
var greenCircle = new Kinetic.Circle({
x: 40,
y: 40,
radius: 20,
strokeWidth: 4,
fill: 'green',
stroke: 'black',
opacity: 0.5
});
var circle = new Kinetic.Circle({ var circle = new Kinetic.Circle({
x: 380, x: 380,
y: stage.getHeight() / 2, y: stage.getHeight() / 2,
radius: 70, radius: 70,
strokeWidth: 4, strokeWidth: 4,
fill: 'red', fill: 'red',
stroke: 'black' stroke: 'black',
opacity: 0.5
}); });
circle.setDraggable(true); circle.setDraggable(true);
layer.add(circle); layer.add(circle);
layer.add(greenCircle);
stage.add(layer); stage.add(layer);
var top = stage.content.getBoundingClientRect().top; var top = stage.content.getBoundingClientRect().top;
@@ -87,7 +101,7 @@ Test.Modules.DD = {
*/ */
layer.on('dragmove', function() { layer.on('dragmove', function() {
console.log('move'); //console.log('move');
}); });
circle.on('dragend', function() { circle.on('dragend', function() {
@@ -135,6 +149,10 @@ Test.Modules.DD = {
test(dragEnd, 'dragend event was not triggered'); test(dragEnd, 'dragend event was not triggered');
warn(layer.toDataURL() === dataUrls['drag circle after'], 'end data url is incorrect'); warn(layer.toDataURL() === dataUrls['drag circle after'], 'end data url is incorrect');
console.log(layer);
console.log(layer.eventListeners['dragmove']);
}, },
'cancel drag and drop by setting draggable to false': function(containerId) { 'cancel drag and drop by setting draggable to false': function(containerId) {
var stage = new Kinetic.Stage({ var stage = new Kinetic.Stage({