further decoupled scene, hit, and buffer graph drawing. To define a custom hit draw function, you now need to set the drawHitFunc attr.

This commit is contained in:
Eric Rowell 2012-11-18 19:50:50 -08:00
parent e04b979063
commit 27d5031665
14 changed files with 197 additions and 113 deletions

View File

@ -233,11 +233,34 @@ Kinetic.Container.prototype = {
children[n].index = n;
}
},
draw: function(canvas) {
if(Kinetic.Node.prototype._shouldDraw.call(this, canvas)) {
/*
* draw both scene and hit graphs
*/
draw: function() {
this.drawScene();
this.drawHit();
},
drawScene: function() {
if(this.isVisible()) {
var children = this.children, len = children.length;
for(var n = 0; n < len; n++) {
children[n].draw(canvas);
children[n].drawScene();
}
}
},
drawHit: function() {
if(this.isVisible() && this.isListening()) {
var children = this.children, len = children.length;
for(var n = 0; n < len; n++) {
children[n].drawHit();
}
}
},
drawBuffer: function(canvas) {
if(this.isVisible()) {
var children = this.children, len = children.length;
for(var n = 0; n < len; n++) {
children[n].drawBuffer(canvas);
}
}
}

View File

@ -39,7 +39,7 @@ Kinetic.Layer.prototype = {
this.afterDrawFunc = undefined;
this.canvas = new Kinetic.Canvas();
this.canvas.getElement().style.position = 'absolute';
this.bufferCanvas = new Kinetic.Canvas(0, 0, true);
this.hitCanvas = new Kinetic.Canvas(0, 0, true);
// call super constructor
Kinetic.Container.call(this, config);
@ -50,31 +50,13 @@ Kinetic.Layer.prototype = {
* @name draw
* @methodOf Kinetic.Layer.prototype
*/
draw: function(canvas) {
draw: function() {
// before draw handler
if(this.beforeDrawFunc !== undefined) {
this.beforeDrawFunc.call(this);
}
var canvases = [];
if(canvas) {
canvases.push(canvas);
}
else {
canvases.push(this.getCanvas());
canvases.push(this.bufferCanvas);
}
var length = canvases.length;
for(var n = 0; n < length; n++) {
var canvas = canvases[n];
if(Kinetic.Node.prototype._shouldDraw.call(this, canvas)) {
if(this.attrs.clearBeforeDraw) {
canvas.clear();
}
Kinetic.Container.prototype.draw.call(this, canvas);
}
}
Kinetic.Container.prototype.draw.call(this);
// after draw handler
if(this.afterDrawFunc !== undefined) {
@ -82,22 +64,27 @@ Kinetic.Layer.prototype = {
}
},
/**
* draw children nodes on buffer. this includes any groups
* draw children nodes on hit. this includes any groups
* or shapes
* @name drawBuffer
* @name drawHit
* @methodOf Kinetic.Layer.prototype
*/
drawBuffer: function() {
this.draw(this.bufferCanvas);
drawHit: function() {
this.hitCanvas.clear();
Kinetic.Container.prototype.drawHit.call(this);
},
/**
* draw children nodes on scene. this includes any groups
* or shapes
* @name drawScene
* @methodOf Kinetic.Layer.prototype
* @param {Kinetic.Canvas} [canvas]
*/
drawScene: function() {
this.draw(this.getCanvas());
if(this.attrs.clearBeforeDraw) {
this.getCanvas().clear();
}
Kinetic.Container.prototype.drawScene.call(this);
},
/**
* set before draw handler
@ -146,11 +133,11 @@ Kinetic.Layer.prototype = {
Kinetic.Node.prototype.setVisible.call(this, visible);
if(visible) {
this.canvas.element.style.display = 'block';
this.bufferCanvas.element.style.display = 'block';
this.hitCanvas.element.style.display = 'block';
}
else {
this.canvas.element.style.display = 'none';
this.bufferCanvas.element.style.display = 'none';
this.hitCanvas.element.style.display = 'none';
}
},
setZIndex: function(index) {

View File

@ -734,16 +734,16 @@ Kinetic.Node = (function() {
var quality = config && config.quality ? config.quality : null;
var canvas;
//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 hit canvas
if(config && config.width && config.height) {
canvas = new Kinetic.Canvas(config.width, config.height);
}
else {
canvas = this.getStage().canvas;
canvas = this.getStage().bufferCanvas;
canvas.clear();
}
this.draw(canvas);
this.drawBuffer(canvas);
return canvas.toDataURL(mimeType, quality);
},
/**
@ -953,9 +953,6 @@ Kinetic.Node = (function() {
for(var i = 0; i < len; i++) {
events[i].handler.apply(this, [evt]);
}
},
_shouldDraw: function(canvas) {
return (this.isVisible() && (!canvas || canvas.getContext().type === 'scene' || this.getListening()));
}
};

View File

@ -108,8 +108,8 @@ Kinetic.Shape = (function() {
if(context.type === 'scene') {
this._strokeScene(context);
}
else if(context.type === 'buffer') {
this._strokeBuffer(context);
else if(context.type === 'hit') {
this._strokeHit(context);
}
},
_strokeScene: function(context) {
@ -132,7 +132,7 @@ Kinetic.Shape = (function() {
}
}
},
_strokeBuffer: function(context) {
_strokeHit: function(context) {
var strokeWidth = this.getStrokeWidth(), stroke = this.colorKey;
if(stroke || strokeWidth) {
context.save();
@ -172,8 +172,8 @@ Kinetic.Shape = (function() {
if(context.type === 'scene') {
this._fillScene(context);
}
else if(context.type === 'buffer') {
this._fillBuffer(context);
else if(context.type === 'hit') {
this._fillHit(context);
}
},
_fillScene: function(context) {
@ -240,7 +240,7 @@ Kinetic.Shape = (function() {
this.fill(context);
}
},
_fillBuffer: function(context) {
_fillHit: function(context) {
context.save();
context.fillStyle = this.colorKey;
context.fill(context);
@ -418,21 +418,21 @@ Kinetic.Shape = (function() {
intersects: function() {
var pos = Kinetic.Type._getXY(Array.prototype.slice.call(arguments));
var stage = this.getStage();
var bufferCanvas = stage.bufferCanvas;
bufferCanvas.clear();
this.draw(bufferCanvas);
var p = bufferCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data;
var hitCanvas = stage.hitCanvas;
hitCanvas.clear();
this.drawBuffer(hitCanvas);
var p = hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data;
return p[3] > 0;
},
remove: function() {
Kinetic.Node.prototype.remove.call(this);
delete Kinetic.Global.shapes[this.colorKey];
},
draw: function(canvas) {
var attrs = this.attrs, drawFunc = attrs.drawFunc, drawBufferFunc = attrs.drawBufferFunc;
drawBuffer: function(canvas) {
var attrs = this.attrs, drawFunc = attrs.drawFunc, context = canvas.getContext();
if(drawFunc && Kinetic.Node.prototype._shouldDraw.call(this, canvas)) {
var stage = this.getStage(), context = canvas.getContext(), family = [], parent = this.parent, type = canvas.getContext().type;
if(drawFunc && this.isVisible()) {
var stage = this.getStage(), family = [], parent = this.parent;
family.unshift(this);
while(parent) {
@ -447,17 +447,67 @@ Kinetic.Shape = (function() {
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
if(type === 'scene') {
this.applyOpacity(context);
}
this.applyOpacity(context);
this.applyLineJoin(context);
this.applyLineCap(context);
this.appliedShadow = false;
var func = (type === 'buffer' && drawBufferFunc) ? drawBufferFunc : drawFunc;
drawFunc.call(this, context);
context.restore();
}
},
drawScene: function() {
var attrs = this.attrs, drawFunc = attrs.drawFunc, context = this.getLayer().getCanvas().getContext();
func.call(this, canvas.getContext());
if(drawFunc && this.isVisible()) {
var stage = this.getStage(), family = [], parent = this.parent;
family.unshift(this);
while(parent) {
family.unshift(parent);
parent = parent.parent;
}
context.save();
var len = family.length;
for(var n = 0; n < len; n++) {
var node = family[n], t = node.getTransform(), m = t.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
this.applyOpacity(context);
this.applyLineJoin(context);
this.applyLineCap(context);
this.appliedShadow = false;
drawFunc.call(this, context);
context.restore();
}
},
drawHit: function() {
var attrs = this.attrs, drawFunc = attrs.drawHitFunc || attrs.drawFunc, context = this.getLayer().hitCanvas.getContext();
if(drawFunc && this.isVisible() && this.isListening()) {
var stage = this.getStage(), family = [], parent = this.parent;
family.unshift(this);
while(parent) {
family.unshift(parent);
parent = parent.parent;
}
context.save();
var len = family.length;
for(var n = 0; n < len; n++) {
var node = family[n], t = node.getTransform(), m = t.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
// don't draw shadows on hit context
this.applyLineJoin(context);
this.applyLineCap(context);
drawFunc.call(this, context);
context.restore();
}
}
@ -465,7 +515,7 @@ Kinetic.Shape = (function() {
Kinetic.Global.extend(Shape, Kinetic.Node);
// add getters and setters
Kinetic.Node.addGettersSetters(Shape, ['stroke', 'lineJoin', 'strokeWidth', 'drawFunc', 'drawBufferFunc', 'cornerRadius']);
Kinetic.Node.addGettersSetters(Shape, ['stroke', 'lineJoin', 'strokeWidth', 'drawFunc', 'drawHitFunc', 'cornerRadius']);
Kinetic.Node.addGetters(Shape, ['shadow', 'fill']);
/**
@ -498,10 +548,10 @@ Kinetic.Shape = (function() {
*/
/**
* set draw buffer function used for hit detection
* @name setDrawBufferFunc
* set draw hit function used for hit detection
* @name setDrawHitFunc
* @methodOf Kinetic.Shape.prototype
* @param {Function} drawBufferFunc drawing function used for hit detection
* @param {Function} drawHitFunc drawing function used for hit detection
*/
/**
@ -549,8 +599,8 @@ Kinetic.Shape = (function() {
*/
/**
* get draw buffer function
* @name getDrawBufferFunc
* get draw hit function
* @name getDrawHitFunc
* @methodOf Kinetic.Shape.prototype
*/

View File

@ -60,11 +60,17 @@ Kinetic.Stage.prototype = {
this.setAttr('container', container);
},
/**
* draw layers
* draw layer scenes
* @name draw
* @methodOf Kinetic.Stage.prototype
*/
/**
* draw layer hits
* @name drawHit
* @methodOf Kinetic.Stage.prototype
*/
/**
* set height
* @name setHeight
@ -178,9 +184,9 @@ Kinetic.Stage.prototype = {
var mimeType = config && config.mimeType ? config.mimeType : null;
var quality = config && config.quality ? config.quality : null;
/*
* need to create a canvas element rather than using the buffer canvas
* need to create a canvas element rather than using the hit canvas
* because this method is asynchonous which means that other parts of the
* code could modify the buffer canvas before it's finished
* code could modify the hit canvas before it's finished
*/
var width = config && config.width ? config.width : this.attrs.width;
var height = config && config.height ? config.height : this.attrs.height;
@ -248,8 +254,8 @@ Kinetic.Stage.prototype = {
for(var n = layers.length - 1; n >= 0; n--) {
var layer = layers[n];
if(layer.isVisible() && layer.isListening()) {
var p = layer.bufferCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data;
// this indicates that a buffer pixel may have been found
var p = layer.hitCanvas.context.getImageData(Math.round(pos.x), Math.round(pos.y), 1, 1).data;
// this indicates that a hit pixel may have been found
if(p[3] === 255) {
var colorKey = Kinetic.Type._rgbToHex(p[0], p[1], p[2]);
shape = Kinetic.Global.shapes[colorKey];
@ -284,14 +290,14 @@ Kinetic.Stage.prototype = {
this.content.style.width = width + 'px';
this.content.style.height = height + 'px';
this.canvas.setSize(width, height);
this.bufferCanvas.setSize(width, height);
this.hitCanvas.setSize(width, height);
// set user defined layer dimensions
var layers = this.children;
for(var n = 0; n < layers.length; n++) {
var layer = layers[n];
layer.getCanvas().setSize(width, height);
layer.bufferCanvas.setSize(width, height);
layer.hitCanvas.setSize(width, height);
layer.draw();
}
}
@ -303,7 +309,7 @@ Kinetic.Stage.prototype = {
add: function(layer) {
Kinetic.Container.prototype.add.call(this, layer);
layer.canvas.setSize(this.attrs.width, this.attrs.height);
layer.bufferCanvas.setSize(this.attrs.width, this.attrs.height);
layer.hitCanvas.setSize(this.attrs.width, this.attrs.height);
// draw layer and append canvas to container
layer.draw();
@ -562,8 +568,8 @@ Kinetic.Stage.prototype = {
this.content.className = 'kineticjs-content';
this.attrs.container.appendChild(this.content);
this.canvas = new Kinetic.Canvas();
this.bufferCanvas = new Kinetic.Canvas(0, 0, true);
this.bufferCanvas = new Kinetic.Canvas();
this.hitCanvas = new Kinetic.Canvas(0, 0, true);
this._resizeDOM();
},

View File

@ -20,8 +20,8 @@ Kinetic.Image.prototype = {
this.shapeType = "Image";
config.drawFunc = this.drawFunc;
if(!config.drawBufferFunc) {
config.drawBufferFunc = this.drawBufferFunc;
if(!config.drawHitFunc) {
config.drawHitFunc = this.drawHitFunc;
}
// call super constructor
@ -58,13 +58,19 @@ Kinetic.Image.prototype = {
}
}
},
drawBufferFunc: function(context) {
var width = this.getWidth(), height = this.getHeight();
drawHitFunc: function(context) {
var width = this.getWidth(), height = this.getHeight(), imageBuffer = this.imageBuffer;
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
this.fill(context);
if(imageBuffer) {
this.drawImage(context, this.imageBuffer, 0, 0, width, height);
}
else {
this.fill(context);
}
this.stroke(context);
},
/**
@ -73,7 +79,7 @@ Kinetic.Image.prototype = {
* @methodOf Kinetic.Image.prototype
* @param {Object} config
* @param {Function} filter filter function
* @param {Object} [config] optional config object used to configure filter
* @param {Object} [config] optional config object used to configure filter
* @param {Function} [callback] callback function to be called once
* filter has been applied
*/

View File

@ -20,8 +20,8 @@ Kinetic.Sprite.prototype = {
this.shapeType = "Sprite";
config.drawFunc = this.drawFunc;
if(!config.drawBufferFunc) {
config.drawBufferFunc = this.drawBufferFunc;
if(!config.drawHitFunc) {
config.drawHitFunc = this.drawHitFunc;
}
// call super constructor
@ -53,7 +53,7 @@ Kinetic.Sprite.prototype = {
this.drawImage(context, this.attrs.image, f.x, f.y, f.width, f.height, 0, 0, f.width, f.height);
}
},
drawBufferFunc: function(context) {
drawHitFunc: function(context) {
var anim = this.attrs.animation;
var index = this.attrs.index;
var f = this.attrs.animations[anim][index];

View File

@ -4,7 +4,7 @@
* @param {Number} width
* @param {Number} height
*/
Kinetic.Canvas = function(width, height, isBuffer) {
Kinetic.Canvas = function(width, height, isHit) {
this.element = document.createElement('canvas');
this.context = this.element.getContext('2d');
@ -13,7 +13,7 @@ Kinetic.Canvas = function(width, height, isBuffer) {
this.element.height = height || 0;
// set type
this.context.type = isBuffer ? 'buffer' : 'scene';
this.context.type = isHit ? 'hit' : 'scene';
};
Kinetic.Canvas.prototype = {

View File

@ -39,8 +39,8 @@ function log(message) {
console.log('LOG: ' + message);
}
function showBuffer(layer) {
document.body.appendChild(layer.bufferCanvas.element);
function showHit(layer) {
document.body.appendChild(layer.hitCanvas.element);
}
function Test() {

View File

@ -859,8 +859,8 @@ Test.Modules.EVENT = {
}
};
Test.Modules['BUFFER FUNCS'] = {
'test custom circle buffer function': function(containerId) {
Test.Modules['HIT FUNCS'] = {
'test custom circle hit function': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
@ -875,7 +875,7 @@ Test.Modules['BUFFER FUNCS'] = {
strokeWidth: 4,
fill: 'red',
stroke: 'black',
drawBufferFunc: function(context) {
drawHitFunc: function(context) {
context.beginPath();
context.arc(0, 0, this.getRadius() + 100, 0, Math.PI * 2, true);
context.closePath();
@ -929,7 +929,7 @@ Test.Modules['BUFFER FUNCS'] = {
// set drawBufferFunc with setter
circle.setDrawBufferFunc(function(context) {
circle.setDrawHitFunc(function(context) {
context.beginPath();
context.arc(0, 0, this.getRadius() - 50, 0, Math.PI * 2, true);
context.closePath();
@ -937,7 +937,7 @@ Test.Modules['BUFFER FUNCS'] = {
this.stroke(context);
});
layer.drawBuffer();
layer.drawHit();
// move mouse far outside circle
stage._mousemove({

View File

@ -712,7 +712,7 @@ Test.Modules.MANUAL = {
layer.add(redCircle);
stage.add(layer);
},
'*DRAG AND DROP - drag and drop elastic star with shadow': function(containerId) {
'DRAG AND DROP - drag and drop elastic star with shadow': function(containerId) {
var stage = new Kinetic.Stage({
container: containerId,
width: 578,
@ -784,7 +784,7 @@ Test.Modules.MANUAL = {
})
});
showBuffer(layer);
showHit(layer);
},
'DRAG AND DROP - two draggable shapes': function(containerId) {
var stage = new Kinetic.Stage({
@ -845,7 +845,7 @@ Test.Modules.MANUAL = {
layer.add(Circle);
stage.add(layer);
showBuffer(layer)
showHit(layer)
},
'DRAG AND DROP - draggable true false': function(containerId) {
var stage = new Kinetic.Stage({

View File

@ -79,9 +79,9 @@ Test.Modules.NODE = {
stage.add(layer);
rect.setListening(false);
layer.drawBuffer();
layer.drawHit();
showBuffer(layer);
showHit(layer);
},
'group to image': function(containerId) {
var stage = new Kinetic.Stage({
@ -317,10 +317,23 @@ Test.Modules.NODE = {
rect.on('tap', function() {
taps.push(this.attrs.myAttr);
});
var clone = group.clone({
x: 300,
name: 'groupClone'
});
showHit(layer);
layer.add(group);
layer.add(clone);
stage.add(layer);
test(clone.getX() === 300, 'clone x should be 300');
test(clone.getY() === 0, 'clone y should be 50');
@ -331,11 +344,6 @@ Test.Modules.NODE = {
test(group.getChildren().length === 2, 'group should have two children');
test(clone.getChildren().length === 2, 'clone should have two children');
layer.add(group);
layer.add(clone);
stage.add(layer);
test(group.get('.myText')[0].getTextFill() === 'blue', 'group text should be blue');
test(clone.get('.myText')[0].getTextFill() === 'blue', 'clone text should be blue');
clone.get('.myText')[0].setTextFill('black');
@ -345,10 +353,10 @@ Test.Modules.NODE = {
myAttr: 'clone rect'
});
/*
* Make sure that when we change a clone object attr that the rect object
* attr isn't updated by reference
*/
// Make sure that when we change a clone object attr that the rect object
// attr isn't updated by reference
test(group.get('.myText')[0].getTextFill() === 'blue', 'group text should be blue');
test(clone.get('.myText')[0].getTextFill() === 'black', 'clone text should be blue');
@ -377,7 +385,10 @@ Test.Modules.NODE = {
clone.get('.myRect')[0].simulate('tap');
test(taps.toString() === 'group rect,clone rect', 'tap order should be group rect followed by clone rect');
stage.draw();
},
'test on attr change': function(containerId) {
var stage = new Kinetic.Stage({
@ -752,20 +763,25 @@ Test.Modules.NODE = {
});
layer.add(cachedShape);
layer.draw();
//console.log(layer.toDataURL());
cachedShape.createImageBuffer(function() {
layer.draw();
//console.log(layer.toDataURL());
warn(dataUrls['regular and cahced polygon'] === layer.toDataURL(), 'regular and cached polygon layer data url is incorrect');
//document.body.appendChild(layer.bufferCanvas.element)
});
}
});
/*
group.toImage({
callback: function(imageObj) {
test(Kinetic.Type._isElement(imageObj), 'group toImage() should be an image object');
@ -781,9 +797,9 @@ Test.Modules.NODE = {
test(Kinetic.Type._isElement(imageObj), 'stage toImage() should be an image object');
}
});
*/
//document.body.appendChild(layer.bufferCanvas.element)
showHit(layer);
},
'hide group': function(containerId) {
var stage = new Kinetic.Stage({

View File

@ -106,7 +106,6 @@ Test.Modules.CIRCLE = {
offset: [-200, -70]
});
//document.body.appendChild(layer.bufferCanvas.element)
};
imageObj.src = '../assets/darth-vader.jpg';
@ -160,7 +159,7 @@ Test.Modules.CIRCLE = {
test(fill.colorStops.length === 6, 'fill colorStops length should be 6');
//document.body.appendChild(layer.bufferCanvas.element)
},
'add circle': function(containerId) {
@ -216,7 +215,7 @@ Test.Modules.CIRCLE = {
test(circle.getName() === 'myCircle', 'circle name should be myCircle');
document.body.appendChild(layer.bufferCanvas.element)
},
'add circle with opacity': function(containerId) {
var stage = new Kinetic.Stage({

View File

@ -115,7 +115,7 @@ Test.Modules.Text = {
//document.body.appendChild(layer.bufferCanvas.element)
//layer.setListening(false);
layer.drawBuffer();
layer.drawHit();
},
'test size setters and getters': function(containerId) {