Implement match.inside and match.overlapping in #getItems(match).

Closes #275
This commit is contained in:
Jürg Lehni 2014-10-20 23:35:47 +02:00
parent 99ef521af4
commit e7707cd354
2 changed files with 71 additions and 10 deletions

View file

@ -1905,15 +1905,26 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
* of the full object, not partial matching (e.g. only providing the x- * of the full object, not partial matching (e.g. only providing the x-
* coordinate to match all points with that x-value). Partial matching * coordinate to match all points with that x-value). Partial matching
* does work for {@link Item#data}. * does work for {@link Item#data}.
*
* Matching items against a rectangular area is also possible, by setting
* either {@code match.inside} or {@code match.overlapping} to a rectangle
* describing the area in which the items either have to be fully or partly
* contained.
*
* See {@link Project#getItems(match)} for a selection of illustrated * See {@link Project#getItems(match)} for a selection of illustrated
* examples. * examples.
* *
* @option match.inside {Rectangle} the rectangle in which the items need to
* be fully contained.
* @option match.overlapping {Rectangle} the rectangle with which the need
* to be at least partly overlap.
*
* @see #matches(match) * @see #matches(match)
* @param {Object} match the criteria to match against. * @param {Object} match the criteria to match against.
* @return {Item[]} the list of matching descendant items. * @return {Item[]} the list of matching descendant items.
*/ */
getItems: function(match) { getItems: function(match) {
return Item._getItems(this._children, match); return Item._getItems(this._children, match, this._matrix);
}, },
/** /**
@ -1932,25 +1943,66 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
* @return {Item} the first descendant item matching the given criteria. * @return {Item} the first descendant item matching the given criteria.
*/ */
getItem: function(match) { getItem: function(match) {
return Item._getItems(this._children, match, true)[0] || null; return Item._getItems(this._children, match, this._matrix, null, true)
[0] || null;
}, },
statics: { statics: {
// NOTE: We pass children instead of item as first argument so the // NOTE: We pass children instead of item as first argument so the
// method can be used for Project#layers as well in Project. // method can be used for Project#layers as well in Project.
_getItems: function _getItems(children, match, firstOnly) { _getItems: function _getItems(children, match, matrix, param,
var items = []; firstOnly) {
if (!param) {
// Set up a couple of "side-car" values for the recursive calls
// of _getItems below, mainly related to the handling of
// inside // overlapping:
var overlapping = match.overlapping,
inside = match.inside,
// If overlapping is set, we also perform the inside check:
bounds = overlapping || inside,
rect = bounds && Rectangle.read([bounds]);
param = {
items: [], // The list to contain the results.
inside: rect,
overlapping: overlapping && new Path.Rectangle({
rectangle: rect,
insert: false
})
};
// Create a copy of the match object that doesn't contain the
// `inside` and `overlapping` properties.
if (bounds)
match = Base.set({}, match,
{ inside: true, overlapping: true });
}
var items = param.items,
inside = param.inside,
overlapping = param.overlapping;
matrix = inside && (matrix || new Matrix());
for (var i = 0, l = children && children.length; i < l; i++) { for (var i = 0, l = children && children.length; i < l; i++) {
var child = children[i]; var child = children[i],
if (child.matches(match)) { childMatrix = matrix && matrix.chain(child._matrix),
add = true;
if (inside) {
var bounds = child.getBounds(childMatrix);
// Regardless of the setting of inside / overlapping, if the
// bounds don't even overlap, we can skip this child.
if (!inside.intersects(bounds))
continue;
if (!(inside && inside.contains(bounds)) && !(overlapping
&& overlapping.intersects(child, childMatrix)))
add = false;
}
if (add && child.matches(match)) {
items.push(child); items.push(child);
if (firstOnly) if (firstOnly)
return items; break;
} }
var res = _getItems(child._children, match, firstOnly); _getItems(child._children, match,
items.push.apply(items, res); childMatrix, param,
firstOnly);
if (firstOnly && items.length > 0) if (firstOnly && items.length > 0)
return items; break;
} }
return items; return items;
} }

View file

@ -348,6 +348,15 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
* of the full object, not partial matching (e.g. only providing the x- * of the full object, not partial matching (e.g. only providing the x-
* coordinate to match all points with that x-value). Partial matching * coordinate to match all points with that x-value). Partial matching
* does work for {@link Item#data}. * does work for {@link Item#data}.
* Matching items against a rectangular area is also possible, by setting
* either {@code match.inside} or {@code match.overlapping} to a rectangle
* describing the area in which the items either have to be fully or partly
* contained.
*
* @option match.inside {Rectangle} the rectangle in which the items need to
* be fully contained.
* @option match.overlapping {Rectangle} the rectangle with which the need
* to be at least partly overlap.
* *
* @example {@paperscript} // Fetch all selected path items: * @example {@paperscript} // Fetch all selected path items:
* var path1 = new Path.Circle({ * var path1 = new Path.Circle({