konva/src/Container.js

588 lines
16 KiB
JavaScript
Raw Normal View History

(function() {
'use strict';
/**
2015-04-08 23:26:43 +08:00
* Container constructor.  Containers are used to contain nodes or other containers
* @constructor
* @memberof Konva
* @augments Konva.Node
* @abstract
* @param {Object} config
* @@nodeParams
* @@containerParams
*/
Konva.Container = function(config) {
this.__init(config);
};
Konva.Util.addMethods(Konva.Container, {
__init: function(config) {
this.children = new Konva.Collection();
Konva.Node.call(this, config);
},
/**
2015-01-27 15:07:51 +08:00
* returns a {@link Konva.Collection} of direct descendant nodes
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {Function} [filterFunc] filter function
2015-01-27 15:07:51 +08:00
* @returns {Konva.Collection}
* @example
2014-04-04 11:17:09 +08:00
* // get all children
* var children = layer.getChildren();
*
2014-04-04 11:17:09 +08:00
* // get only circles
* var circles = layer.getChildren(function(node){
* return node.getClassName() === 'Circle';
* });
*/
getChildren: function(filterFunc) {
if (!filterFunc) {
return this.children;
}
var results = new Konva.Collection();
this.children.each(function(child) {
if (filterFunc(child)) {
results.push(child);
}
});
return results;
},
/**
* determine if node has children
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2014-01-13 01:07:57 +08:00
* @returns {Boolean}
*/
hasChildren: function() {
return this.getChildren().length > 0;
},
/**
* remove all children
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
*/
removeChildren: function() {
var children = Konva.Collection.toCollection(this.children);
var child;
for (var i = 0; i < children.length; i++) {
child = children[i];
// reset parent to prevent many _setChildrenIndices calls
delete child.parent;
child.index = 0;
child.remove();
}
children = null;
this.children = new Konva.Collection();
return this;
},
/**
2013-06-07 14:03:00 +08:00
* destroy all children
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2013-06-07 14:03:00 +08:00
*/
destroyChildren: function() {
var children = Konva.Collection.toCollection(this.children);
var child;
for (var i = 0; i < children.length; i++) {
child = children[i];
// reset parent to prevent many _setChildrenIndices calls
delete child.parent;
child.index = 0;
child.destroy();
}
children = null;
this.children = new Konva.Collection();
return this;
},
/**
* Add node or nodes to container.
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {...Konva.Node} child
2014-01-13 01:05:46 +08:00
* @returns {Container}
* @example
* layer.add(shape1, shape2, shape3);
*/
add: function(child) {
if (arguments.length > 1) {
for (var i = 0; i < arguments.length; i++) {
this.add(arguments[i]);
}
return this;
}
if (child.getParent()) {
child.moveTo(this);
return this;
}
var children = this.children;
this._validateAdd(child);
child.index = children.length;
child.parent = this;
children.push(child);
this._fire('add', {
child: child
});
// if node under drag we need to update drag animation
if (Konva.DD && child.isDragging()) {
Konva.DD.anim.setLayers(child.getLayer());
}
// chainable
return this;
},
destroy: function() {
// destroy children
if (this.hasChildren()) {
this.destroyChildren();
}
// then destroy self
Konva.Node.prototype.destroy.call(this);
return this;
},
/**
2015-01-27 15:07:51 +08:00
* return a {@link Konva.Collection} of nodes that match the selector. Use '#' for id selections
* and '.' for name selections. You can also select by type or class name. Pass multiple selectors
* separated by a space.
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {String} selector
2014-01-13 01:07:06 +08:00
* @returns {Collection}
* @example
2014-04-04 11:17:09 +08:00
* // select node with id foo
* var node = stage.find('#foo');
*
2014-04-04 11:17:09 +08:00
* // select nodes with name bar inside layer
* var nodes = layer.find('.bar');
*
2014-04-04 11:17:09 +08:00
* // select all groups inside layer
* var nodes = layer.find('Group');
*
2014-04-04 11:17:09 +08:00
* // select all rectangles inside layer
* var nodes = layer.find('Rect');
*
2014-04-04 11:17:09 +08:00
* // select node with an id of foo or a name of bar inside layer
* var nodes = layer.find('#foo, .bar');
*/
find: function(selector) {
var retArr = [],
selectorArr = selector.replace(/ /g, '').split(','),
len = selectorArr.length,
n,
i,
sel,
arr,
node,
children,
clen;
for (n = 0; n < len; n++) {
sel = selectorArr[n];
if (!Konva.Util.isValidSelector(sel)) {
Konva.Util.warn(
'Selector "' +
sel +
'" is invalid. Allowed selectors examples are "#foo", ".bar" or "Group".'
);
Konva.Util.warn(
'If you have a custom shape with such className, please change it to start with upper letter like "Triangle".'
);
Konva.Util.warn('Konva is awesome, right?');
}
// id selector
if (sel.charAt(0) === '#') {
node = this._getNodeById(sel.slice(1));
if (node) {
retArr.push(node);
}
} else if (sel.charAt(0) === '.') {
// name selector
arr = this._getNodesByName(sel.slice(1));
retArr = retArr.concat(arr);
} else {
// unrecognized selector, pass to children
children = this.getChildren();
clen = children.length;
for (i = 0; i < clen; i++) {
retArr = retArr.concat(children[i]._get(sel));
}
}
}
return Konva.Collection.toCollection(retArr);
},
/**
2015-02-03 11:30:27 +08:00
* return a first node from `find` method
* @method
* @memberof Konva.Container.prototype
* @param {String} selector
* @returns {Konva.Node}
* @example
* // select node with id foo
* var node = stage.findOne('#foo');
*
* // select node with name bar inside layer
* var nodes = layer.findOne('.bar');
*/
findOne: function(selector) {
return this.find(selector)[0];
},
_getNodeById: function(key) {
var node = Konva.ids[key];
if (node !== undefined && this.isAncestorOf(node)) {
return node;
}
return null;
},
_getNodesByName: function(key) {
var arr = Konva.names[key] || [];
return this._getDescendants(arr);
},
_get: function(selector) {
var retArr = Konva.Node.prototype._get.call(this, selector);
var children = this.getChildren();
var len = children.length;
for (var n = 0; n < len; n++) {
retArr = retArr.concat(children[n]._get(selector));
}
return retArr;
},
// extenders
toObject: function() {
var obj = Konva.Node.prototype.toObject.call(this);
obj.children = [];
var children = this.getChildren();
var len = children.length;
for (var n = 0; n < len; n++) {
var child = children[n];
obj.children.push(child.toObject());
}
return obj;
},
_getDescendants: function(arr) {
var retArr = [];
var len = arr.length;
for (var n = 0; n < len; n++) {
var node = arr[n];
if (this.isAncestorOf(node)) {
retArr.push(node);
}
}
return retArr;
},
/**
* determine if node is an ancestor
* of descendant
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {Konva.Node} node
*/
isAncestorOf: function(node) {
var parent = node.getParent();
while (parent) {
if (parent._id === this._id) {
return true;
}
parent = parent.getParent();
}
return false;
},
clone: function(obj) {
// call super method
var node = Konva.Node.prototype.clone.call(this, obj);
this.getChildren().each(function(no) {
node.add(no.clone());
});
return node;
},
/**
* get all shapes that intersect a point. Note: because this method must clear a temporary
* canvas and redraw every shape inside the container, it should only be used for special sitations
2015-01-27 15:07:51 +08:00
* because it performs very poorly. Please use the {@link Konva.Stage#getIntersection} method if at all possible
* because it performs much better
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {Object} pos
* @param {Number} pos.x
* @param {Number} pos.y
* @returns {Array} array of shapes
*/
getAllIntersections: function(pos) {
var arr = [];
this.find('Shape').each(function(shape) {
if (shape.isVisible() && shape.intersects(pos)) {
arr.push(shape);
}
});
return arr;
},
_setChildrenIndices: function() {
this.children.each(function(child, n) {
child.index = n;
});
},
drawScene: function(can, top, caching) {
var layer = this.getLayer(),
canvas = can || (layer && layer.getCanvas()),
context = canvas && canvas.getContext(),
cachedCanvas = this._cache.canvas,
cachedSceneCanvas = cachedCanvas && cachedCanvas.scene;
if (this.isVisible()) {
if (!caching && cachedSceneCanvas) {
context.save();
layer._applyTransform(this, context, top);
this._drawCachedSceneCanvas(context);
context.restore();
} else {
this._drawChildren(canvas, 'drawScene', top, false, caching);
}
}
return this;
},
drawHit: function(can, top, caching) {
var layer = this.getLayer(),
canvas = can || (layer && layer.hitCanvas),
context = canvas && canvas.getContext(),
cachedCanvas = this._cache.canvas,
cachedHitCanvas = cachedCanvas && cachedCanvas.hit;
if (this.shouldDrawHit(canvas)) {
if (layer) {
layer.clearHitCache();
}
if (!caching && cachedHitCanvas) {
context.save();
layer._applyTransform(this, context, top);
this._drawCachedHitCanvas(context);
context.restore();
} else {
this._drawChildren(canvas, 'drawHit', top);
}
}
return this;
},
_drawChildren: function(canvas, drawMethod, top, caching, skipBuffer) {
var layer = this.getLayer(),
context = canvas && canvas.getContext(),
clipWidth = this.getClipWidth(),
clipHeight = this.getClipHeight(),
clipFunc = this.getClipFunc(),
hasClip = (clipWidth && clipHeight) || clipFunc,
clipX,
clipY;
if (hasClip && layer) {
context.save();
var transform = this.getAbsoluteTransform(top);
var m = transform.getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
context.beginPath();
if (clipFunc) {
clipFunc.call(this, context, this);
} else {
clipX = this.getClipX();
clipY = this.getClipY();
context.rect(clipX, clipY, clipWidth, clipHeight);
}
context.clip();
m = transform.copy().invert().getMatrix();
context.transform(m[0], m[1], m[2], m[3], m[4], m[5]);
}
this.children.each(function(child) {
child[drawMethod](canvas, top, caching, skipBuffer);
});
if (hasClip) {
context.restore();
}
},
shouldDrawHit: function(canvas) {
var layer = this.getLayer();
var dd = Konva.DD;
var layerUnderDrag = dd &&
Konva.isDragging() &&
Konva.DD.anim.getLayers().indexOf(layer) !== -1;
return (canvas && canvas.isCache) ||
(layer &&
layer.hitGraphEnabled() &&
this.isVisible() &&
!layerUnderDrag);
},
getClientRect: function(skipTransform) {
var minX, minY, maxX, maxY;
var selfRect = {
x: 0,
y: 0,
width: 0,
height: 0
};
this.children.each(function(child) {
var rect = child.getClientRect();
// skip invisible children (like empty groups)
// or don't skip... hmmm...
// if (rect.width === 0 && rect.height === 0) {
// return;
// }
if (minX === undefined) {
// initial value for first child
minX = rect.x;
minY = rect.y;
maxX = rect.x + rect.width;
maxY = rect.y + rect.height;
} else {
minX = Math.min(minX, rect.x);
minY = Math.min(minY, rect.y);
maxX = Math.max(maxX, rect.x + rect.width);
maxY = Math.max(maxY, rect.y + rect.height);
}
});
if (this.children.length !== 0) {
selfRect = {
x: minX,
y: minY,
width: maxX - minX,
height: maxY - minY
};
}
if (!skipTransform) {
return this._transformedRect(selfRect);
}
return selfRect;
}
});
Konva.Util.extend(Konva.Container, Konva.Node);
// deprecated methods
Konva.Container.prototype.get = Konva.Container.prototype.find;
// add getters setters
Konva.Factory.addComponentsGetterSetter(Konva.Container, 'clip', [
'x',
'y',
'width',
'height'
]);
/**
2014-01-06 12:52:09 +08:00
* get/set clip
* @method
2014-01-06 12:52:09 +08:00
* @name clip
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2014-01-06 12:52:09 +08:00
* @param {Object} clip
* @param {Number} clip.x
* @param {Number} clip.y
* @param {Number} clip.width
* @param {Number} clip.height
* @returns {Object}
* @example
2014-04-04 11:17:09 +08:00
* // get clip
* var clip = container.clip();
2014-01-06 12:52:09 +08:00
*
2014-04-04 11:17:09 +08:00
* // set clip
* container.setClip({
* x: 20,
* y: 20,
* width: 20,
* height: 20
* });
*/
Konva.Factory.addGetterSetter(Konva.Container, 'clipX');
/**
2014-01-06 12:52:09 +08:00
* get/set clip x
* @name clipX
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
* @param {Number} x
* @returns {Number}
2014-01-06 12:52:09 +08:00
* @example
2014-04-04 11:17:09 +08:00
* // get clip x
* var clipX = container.clipX();
2014-01-06 12:52:09 +08:00
*
2014-04-04 11:17:09 +08:00
* // set clip x
2014-01-06 12:52:09 +08:00
* container.clipX(10);
*/
Konva.Factory.addGetterSetter(Konva.Container, 'clipY');
/**
2014-01-06 12:52:09 +08:00
* get/set clip y
* @name clipY
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2014-01-06 12:52:09 +08:00
* @param {Number} y
* @returns {Number}
2014-01-06 12:52:09 +08:00
* @example
2014-04-04 11:17:09 +08:00
* // get clip y
* var clipY = container.clipY();
2014-01-06 12:52:09 +08:00
*
2014-04-04 11:17:09 +08:00
* // set clip y
2014-01-06 12:52:09 +08:00
* container.clipY(10);
*/
Konva.Factory.addGetterSetter(Konva.Container, 'clipWidth');
/**
2014-01-06 12:52:09 +08:00
* get/set clip width
* @name clipWidth
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2014-01-06 12:52:09 +08:00
* @param {Number} width
* @returns {Number}
2014-01-06 12:52:09 +08:00
* @example
2014-04-04 11:17:09 +08:00
* // get clip width
* var clipWidth = container.clipWidth();
2014-01-06 12:52:09 +08:00
*
2014-04-04 11:17:09 +08:00
* // set clip width
2014-01-06 12:52:09 +08:00
* container.clipWidth(100);
*/
Konva.Factory.addGetterSetter(Konva.Container, 'clipHeight');
/**
2014-01-06 12:52:09 +08:00
* get/set clip height
* @name clipHeight
* @method
2015-01-27 15:07:51 +08:00
* @memberof Konva.Container.prototype
2014-01-06 12:52:09 +08:00
* @param {Number} height
* @returns {Number}
2014-01-06 12:52:09 +08:00
* @example
2014-04-04 11:17:09 +08:00
* // get clip height
* var clipHeight = container.clipHeight();
2014-01-06 12:52:09 +08:00
*
2014-04-04 11:17:09 +08:00
* // set clip height
2014-01-06 12:52:09 +08:00
* container.clipHeight(100);
*/
Konva.Factory.addGetterSetter(Konva.Container, 'clipFunc');
/**
2016-06-18 23:27:19 +08:00
* get/set clip function
* @name clipFunc
* @method
* @memberof Konva.Container.prototype
* @param {Function} function
* @returns {Function}
* @example
* // get clip function
* var clipFunction = container.clipFunc();
*
* // set clip height
* container.clipFunc(function(ctx) {
* ctx.rect(0, 0, 100, 100);
* });
*/
Konva.Collection.mapMethods(Konva.Container);
})();