mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Implement #hitTestAll() on Item and Project
Along with unit tests and documentation. Closes #536
This commit is contained in:
parent
3ee46ffc5c
commit
4a947317fb
9 changed files with 371 additions and 172 deletions
|
@ -106,7 +106,8 @@ var HitResult = Base.extend(/** @lends HitResult# */{
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
getOptions: function(options) {
|
getOptions: function(args) {
|
||||||
|
var options = args && Base.read(args);
|
||||||
return Base.set({
|
return Base.set({
|
||||||
// Type of item, for instanceof check: Group, Layer, Path,
|
// Type of item, for instanceof check: Group, Layer, Path,
|
||||||
// CompoundPath, Shape, Raster, SymbolItem, ...
|
// CompoundPath, Shape, Raster, SymbolItem, ...
|
||||||
|
|
211
src/item/Item.js
211
src/item/Item.js
|
@ -70,7 +70,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
||||||
data: {}
|
data: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new function() { // // Scope to inject various item event handlers
|
new function() { // Injection scope for various item event handlers
|
||||||
var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
|
var handlers = ['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
|
||||||
'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'];
|
'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'];
|
||||||
return Base.each(handlers,
|
return Base.each(handlers,
|
||||||
|
@ -1628,6 +1628,8 @@ new function() { // // Scope to inject various item event handlers
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* {@grouptitle Geometric Tests}
|
||||||
|
*
|
||||||
* Checks whether the item's geometry contains the given point.
|
* Checks whether the item's geometry contains the given point.
|
||||||
*
|
*
|
||||||
* @example {@paperscript} // Click within and outside the star below
|
* @example {@paperscript} // Click within and outside the star below
|
||||||
|
@ -1710,20 +1712,79 @@ new function() { // // Scope to inject various item event handlers
|
||||||
// found, because all we care for here is there are some or none:
|
// found, because all we care for here is there are some or none:
|
||||||
return this._asPathItem().getIntersections(item._asPathItem(), null,
|
return this._asPathItem().getIntersections(item._asPathItem(), null,
|
||||||
_matrix, true).length > 0;
|
_matrix, true).length > 0;
|
||||||
},
|
}
|
||||||
|
},
|
||||||
|
new function() { // Injection scope for hit-test functions shared with project
|
||||||
|
function hitTest(/* point, options */) {
|
||||||
|
return this._hitTest(
|
||||||
|
Point.read(arguments),
|
||||||
|
HitResult.getOptions(arguments));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hitTestAll(/* point, options */) {
|
||||||
|
var point = Point.read(arguments),
|
||||||
|
options = HitResult.getOptions(arguments),
|
||||||
|
callback = options.match,
|
||||||
|
results = [];
|
||||||
|
options = Base.set({}, options, {
|
||||||
|
match: function(hit) {
|
||||||
|
if (!callback || callback(hit))
|
||||||
|
results.push(hit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._hitTest(point, options);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hitTestChildren(point, options, _exclude) {
|
||||||
|
// NOTE: _exclude is only used in Group#_hitTestChildren()
|
||||||
|
var children = this._children;
|
||||||
|
if (children) {
|
||||||
|
// Loop backwards, so items that get drawn last are tested first.
|
||||||
|
for (var i = children.length - 1; i >= 0; i--) {
|
||||||
|
var child = children[i];
|
||||||
|
var res = child !== _exclude && child._hitTest(point, options);
|
||||||
|
if (res)
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Project.inject({
|
||||||
|
hitTest: hitTest,
|
||||||
|
hitTestAll: hitTestAll,
|
||||||
|
_hitTest: hitTestChildren
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
// NOTE: Documentation is in the scope that follows.
|
||||||
|
hitTest: hitTest,
|
||||||
|
hitTestAll: hitTestAll,
|
||||||
|
_hitTestChildren: hitTestChildren,
|
||||||
|
};
|
||||||
|
}, /** @lends Item# */{
|
||||||
/**
|
/**
|
||||||
* Perform a hit-test on the item (and its children, if it is a {@link
|
* {@grouptitle Hit-testing, Fetching and Matching Items}
|
||||||
* Group} or {@link Layer}) at the location of the specified point.
|
|
||||||
*
|
*
|
||||||
* The options object allows you to control the specifics of the hit-test
|
* Performs a hit-test on the item and its children (if it is a {@link
|
||||||
* and may contain a combination of the following values:
|
* Group} or {@link Layer}) at the location of the specified point,
|
||||||
|
* returning the first found hit.
|
||||||
|
*
|
||||||
|
* The options object allows you to control the specifics of the hit-
|
||||||
|
* test and may contain a combination of the following values:
|
||||||
|
*
|
||||||
|
* @name Item#hitTest
|
||||||
|
* @function
|
||||||
*
|
*
|
||||||
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
||||||
* {Number} the tolerance of the hit-test
|
* {Number} the tolerance of the hit-test
|
||||||
* @option options.class {Function} only hit-test again a certain item class
|
* @option options.class {Function} only hit-test again a certain item
|
||||||
* and its sub-classes: {@values Group, Layer, Path, CompoundPath,
|
* class and its sub-classes: {@values Group, Layer, Path,
|
||||||
* Shape, Raster, SymbolItem, PointText, ...}
|
* CompoundPath, Shape, Raster, SymbolItem, PointText, ...}
|
||||||
|
* @option options.match {Function} a match function to be called for each
|
||||||
|
* found hit result: Return `true` to return the result, `false` to keep
|
||||||
|
* searching
|
||||||
* @option [options.fill=true] {Boolean} hit-test the fill of items
|
* @option [options.fill=true] {Boolean} hit-test the fill of items
|
||||||
* @option [options.stroke=true] {Boolean} hit-test the stroke of path
|
* @option [options.stroke=true] {Boolean} hit-test the stroke of path
|
||||||
* items, taking into account the setting of stroke color and width
|
* items, taking into account the setting of stroke color and width
|
||||||
|
@ -1746,19 +1807,33 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* @param {Point} point the point where the hit-test should be performed
|
* @param {Point} point the point where the hit-test should be performed
|
||||||
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||||
* tolerance: settings.hitTolerance }]
|
* tolerance: settings.hitTolerance }]
|
||||||
* @return {HitResult} a hit result object that contains more information
|
* @return {HitResult} a hit result object describing what exactly was hit
|
||||||
* about what exactly was hit or `null` if nothing was hit
|
* or `null` if nothing was hit
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a hit-test on the item and its children (if it is a {@link
|
||||||
|
* Group} or {@link Layer}) at the location of the specified point,
|
||||||
|
* returning all found hits.
|
||||||
|
*
|
||||||
|
* The options object allows you to control the specifics of the hit-
|
||||||
|
* test. See {@link #hitTest(point[, options])} for a list of all options.
|
||||||
|
*
|
||||||
|
* @name Item#hitTestAll
|
||||||
|
* @function
|
||||||
|
* @param {Point} point the point where the hit-test should be performed
|
||||||
|
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||||
|
* tolerance: settings.hitTolerance }]
|
||||||
|
* @return {HitResult[]} hit result objects for all hits, describing what
|
||||||
|
* exactly was hit or `null` if nothing was hit
|
||||||
|
* @see #hitTest(point[, options]);
|
||||||
*/
|
*/
|
||||||
hitTest: function(/* point, options */) {
|
|
||||||
return this._hitTest(
|
|
||||||
Point.read(arguments),
|
|
||||||
HitResult.getOptions(Base.read(arguments)));
|
|
||||||
},
|
|
||||||
|
|
||||||
_hitTest: function(point, options) {
|
_hitTest: function(point, options) {
|
||||||
if (this._locked || !this._visible || this._guide && !options.guides
|
if (this._locked || !this._visible || this._guide && !options.guides
|
||||||
|| this.isEmpty())
|
|| this.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the point is withing roughBounds + tolerance, but only if
|
// Check if the point is withing roughBounds + tolerance, but only if
|
||||||
// this item does not have children, since we'd have to travel up the
|
// this item does not have children, since we'd have to travel up the
|
||||||
|
@ -1783,26 +1858,35 @@ new function() { // // Scope to inject various item event handlers
|
||||||
point = matrix._inverseTransform(point);
|
point = matrix._inverseTransform(point);
|
||||||
// If the matrix is non-reversible, point will now be `null`:
|
// If the matrix is non-reversible, point will now be `null`:
|
||||||
if (!point || !this._children &&
|
if (!point || !this._children &&
|
||||||
!this.getBounds({ internal: true, stroke: true, handle: true })
|
!this.getBounds({ internal: true, stroke: true, handle: true })
|
||||||
.expand(tolerancePadding.multiply(2))._containsPoint(point))
|
.expand(tolerancePadding.multiply(2))._containsPoint(point)) {
|
||||||
return null;
|
return null;
|
||||||
// Filter for type, guides and selected items if that's required.
|
}
|
||||||
|
|
||||||
|
// See if we should check self (own content), by filtering for type,
|
||||||
|
// guides and selected items if that's required.
|
||||||
var checkSelf = !(options.guides && !this._guide
|
var checkSelf = !(options.guides && !this._guide
|
||||||
|| options.selected && !this._selected
|
|| options.selected && !this._selected
|
||||||
// Support legacy Item#type property to match hyphenated
|
// Support legacy Item#type property to match hyphenated
|
||||||
// class-names.
|
// class-names.
|
||||||
|| options.type && options.type !== Base.hyphenate(this._class)
|
|| options.type && options.type !== Base.hyphenate(this._class)
|
||||||
|| options.class && !(this instanceof options.class)),
|
|| options.class && !(this instanceof options.class)),
|
||||||
|
callback = options.match,
|
||||||
that = this,
|
that = this,
|
||||||
res;
|
res;
|
||||||
|
|
||||||
|
function match(hit) {
|
||||||
|
return !callback || hit && callback(hit) ? hit : null;
|
||||||
|
}
|
||||||
|
|
||||||
function checkBounds(type, part) {
|
function checkBounds(type, part) {
|
||||||
var pt = bounds['get' + part]();
|
var pt = bounds['get' + part]();
|
||||||
// Since there are transformations, we cannot simply use a numerical
|
// Since there are transformations, we cannot simply use a numerical
|
||||||
// tolerance value. Instead, we divide by a padding size, see above.
|
// tolerance value. Instead, we divide by a padding size, see above.
|
||||||
if (point.subtract(pt).divide(tolerancePadding).length <= 1)
|
if (point.subtract(pt).divide(tolerancePadding).length <= 1) {
|
||||||
return new HitResult(type, that,
|
return new HitResult(type, that,
|
||||||
{ name: Base.hyphenate(part), point: pt });
|
{ name: Base.hyphenate(part), point: pt });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore top level layers by checking for _parent:
|
// Ignore top level layers by checking for _parent:
|
||||||
|
@ -1810,47 +1894,39 @@ new function() { // // Scope to inject various item event handlers
|
||||||
// Don't get the transformed bounds, check against transformed
|
// Don't get the transformed bounds, check against transformed
|
||||||
// points instead
|
// points instead
|
||||||
var bounds = this.getInternalBounds();
|
var bounds = this.getInternalBounds();
|
||||||
if (options.center)
|
if (options.center) {
|
||||||
res = checkBounds('center', 'Center');
|
res = checkBounds('center', 'Center');
|
||||||
|
}
|
||||||
if (!res && options.bounds) {
|
if (!res && options.bounds) {
|
||||||
// TODO: Move these into a private scope
|
// TODO: Move these into a private scope
|
||||||
var points = [
|
var points = [
|
||||||
'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
|
'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight',
|
||||||
'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'
|
'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'
|
||||||
];
|
];
|
||||||
for (var i = 0; i < 8 && !res; i++)
|
for (var i = 0; i < 8 && !res; i++) {
|
||||||
res = checkBounds('bounds', points[i]);
|
res = checkBounds('bounds', points[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
res = match(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!res) {
|
if (!res) {
|
||||||
options._viewMatrix = viewMatrix;
|
options._viewMatrix = viewMatrix;
|
||||||
options._strokeMatrix = strokeMatrix;
|
|
||||||
res = this._hitTestChildren(point, options)
|
res = this._hitTestChildren(point, options)
|
||||||
|| checkSelf && this._hitTestSelf(point, options)
|
// NOTE: We don't call callback on _hitTestChildren()
|
||||||
|| null;
|
// because that's already called internally.
|
||||||
|
|| checkSelf
|
||||||
|
&& match(this._hitTestSelf(point, options, strokeMatrix))
|
||||||
|
|| null;
|
||||||
// Restore viewMatrix for next child, so appended matrix chains are
|
// Restore viewMatrix for next child, so appended matrix chains are
|
||||||
// calculated correctly.
|
// calculated correctly.
|
||||||
options._viewMatrix = parentViewMatrix;
|
options._viewMatrix = parentViewMatrix;
|
||||||
}
|
}
|
||||||
// Transform the point back to the outer coordinate system.
|
// Transform the point back to the outer coordinate system.
|
||||||
if (res && res.point)
|
if (res && res.point) {
|
||||||
res.point = matrix.transform(res.point);
|
res.point = matrix.transform(res.point);
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
_hitTestChildren: function(point, options, _exclude) {
|
|
||||||
// NOTE: _exclude is only used in Group#_hitTestChildren()
|
|
||||||
var children = this._children;
|
|
||||||
if (children) {
|
|
||||||
// Loop backwards, so items that get drawn last are tested first
|
|
||||||
for (var i = children.length - 1; i >= 0; i--) {
|
|
||||||
var child = children[i];
|
|
||||||
var res = child !== _exclude && child._hitTest(point, options);
|
|
||||||
if (res)
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
_hitTestSelf: function(point, options) {
|
_hitTestSelf: function(point, options) {
|
||||||
|
@ -1860,21 +1936,19 @@ new function() { // // Scope to inject various item event handlers
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@grouptitle Fetching and matching items}
|
|
||||||
*
|
|
||||||
* Checks whether the item matches the criteria described by the given
|
* Checks whether the item matches the criteria described by the given
|
||||||
* object, by iterating over all of its properties and matching against
|
* object, by iterating over all of its properties and matching against
|
||||||
* their values through {@link #matches(name, compare)}.
|
* their values through {@link #matches(name, compare)}.
|
||||||
*
|
*
|
||||||
* See {@link Project#getItems(match)} for a selection of illustrated
|
* See {@link Project#getItems(options)} for a selection of illustrated
|
||||||
* examples.
|
* examples.
|
||||||
*
|
*
|
||||||
* @name Item#matches
|
* @name Item#matches
|
||||||
* @function
|
* @function
|
||||||
*
|
*
|
||||||
* @param {Object|Function} match the criteria to match against
|
* @param {Object|Function} options the criteria to match against
|
||||||
* @return {Boolean} {@true if the item matches all the criteria}
|
* @return {Boolean} {@true if the item matches all the criteria}
|
||||||
* @see #getItems(match)
|
* @see #getItems(options)
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Checks whether the item matches the given criteria. Extended matching is
|
* Checks whether the item matches the given criteria. Extended matching is
|
||||||
|
@ -1884,7 +1958,7 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* points with that x-value). Partial matching does work for
|
* points with that x-value). Partial matching does work for
|
||||||
* {@link Item#data}.
|
* {@link Item#data}.
|
||||||
*
|
*
|
||||||
* See {@link Project#getItems(match)} for a selection of illustrated
|
* See {@link Project#getItems(options)} for a selection of illustrated
|
||||||
* examples.
|
* examples.
|
||||||
*
|
*
|
||||||
* @name Item#matches
|
* @name Item#matches
|
||||||
|
@ -1894,7 +1968,7 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* @param {Object} compare the value, function or regular expression to
|
* @param {Object} compare the value, function or regular expression to
|
||||||
* compare against
|
* compare against
|
||||||
* @return {Boolean} {@true if the item matches the state}
|
* @return {Boolean} {@true if the item matches the state}
|
||||||
* @see #getItems(match)
|
* @see #getItems(options)
|
||||||
*/
|
*/
|
||||||
matches: function(name, compare) {
|
matches: function(name, compare) {
|
||||||
// matchObject() is used to match against objects in a nested manner.
|
// matchObject() is used to match against objects in a nested manner.
|
||||||
|
@ -1965,31 +2039,31 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* that x-value). Partial matching does work for {@link Item#data}.
|
* that x-value). Partial matching does work for {@link Item#data}.
|
||||||
*
|
*
|
||||||
* Matching items against a rectangular area is also possible, by setting
|
* Matching items against a rectangular area is also possible, by setting
|
||||||
* either `match.inside` or `match.overlapping` to a rectangle describing
|
* either `options.inside` or `options.overlapping` to a rectangle describing
|
||||||
* the area in which the items either have to be fully or partly contained.
|
* 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(options)} for a selection of illustrated
|
||||||
* examples.
|
* examples.
|
||||||
*
|
*
|
||||||
* @option [match.recursive=true] {Boolean} whether to loop recursively
|
* @option [options.recursive=true] {Boolean} whether to loop recursively
|
||||||
* through all children, or stop at the current level
|
* through all children, or stop at the current level
|
||||||
* @option match.match {Function} a match function to be called for each
|
* @option options.match {Function} a match function to be called for each
|
||||||
* item, allowing the definition of more flexible item checks that are
|
* item, allowing the definition of more flexible item checks that are
|
||||||
* not bound to properties. If no other match properties are defined,
|
* not bound to properties. If no other match properties are defined,
|
||||||
* this function can also be passed instead of the `match` object
|
* this function can also be passed instead of the `options` object
|
||||||
* @option match.class {Function} the constructor function of the item type
|
* @option options.class {Function} the constructor function of the item type
|
||||||
* to match against
|
* to match against
|
||||||
* @option match.inside {Rectangle} the rectangle in which the items need to
|
* @option options.inside {Rectangle} the rectangle in which the items need to
|
||||||
* be fully contained
|
* be fully contained
|
||||||
* @option match.overlapping {Rectangle} the rectangle with which the items
|
* @option options.overlapping {Rectangle} the rectangle with which the items
|
||||||
* need to at least partly overlap
|
* need to at least partly overlap
|
||||||
*
|
*
|
||||||
* @param {Object|Function} match the criteria to match against
|
* @param {Object|Function} options the criteria to match against
|
||||||
* @return {Item[]} the list of matching descendant items
|
* @return {Item[]} the list of matching descendant items
|
||||||
* @see #matches(match)
|
* @see #matches(options)
|
||||||
*/
|
*/
|
||||||
getItems: function(match) {
|
getItems: function(options) {
|
||||||
return Item._getItems(this, match, this._matrix);
|
return Item._getItems(this, options, this._matrix);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2007,19 +2081,20 @@ new function() { // // Scope to inject various item event handlers
|
||||||
* @return {Item} the first descendant item matching the given criteria
|
* @return {Item} the first descendant item matching the given criteria
|
||||||
* @see #getItems(match)
|
* @see #getItems(match)
|
||||||
*/
|
*/
|
||||||
getItem: function(match) {
|
getItem: function(options) {
|
||||||
return Item._getItems(this, match, this._matrix, null, true)[0] || null;
|
return Item._getItems(this, options, 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(item, match, matrix, param, firstOnly) {
|
_getItems: function _getItems(item, options, matrix, param, firstOnly) {
|
||||||
if (!param) {
|
if (!param) {
|
||||||
// Set up a couple of "side-car" values for the recursive calls
|
// Set up a couple of "side-car" values for the recursive calls
|
||||||
// of _getItems below, mainly related to the handling of
|
// of _getItems below, mainly related to the handling of
|
||||||
// inside / overlapping:
|
// inside / overlapping:
|
||||||
var obj = typeof match === 'object' && match,
|
var obj = typeof options === 'object' && options,
|
||||||
overlapping = obj && obj.overlapping,
|
overlapping = obj && obj.overlapping,
|
||||||
inside = obj && obj.inside,
|
inside = obj && obj.inside,
|
||||||
// If overlapping is set, we also perform the inside check:
|
// If overlapping is set, we also perform the inside check:
|
||||||
|
@ -2037,9 +2112,9 @@ new function() { // // Scope to inject various item event handlers
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
if (obj) {
|
if (obj) {
|
||||||
// Create a copy of the match object that doesn't contain
|
// Create a copy of the options object that doesn't contain
|
||||||
// these special properties:
|
// these special properties:
|
||||||
match = Base.filter({}, match, {
|
options = Base.filter({}, options, {
|
||||||
recursive: true, inside: true, overlapping: true
|
recursive: true, inside: true, overlapping: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2067,13 +2142,13 @@ new function() { // // Scope to inject various item event handlers
|
||||||
|| param.path.intersects(child, childMatrix))))
|
|| param.path.intersects(child, childMatrix))))
|
||||||
add = false;
|
add = false;
|
||||||
}
|
}
|
||||||
if (add && child.matches(match)) {
|
if (add && child.matches(options)) {
|
||||||
items.push(child);
|
items.push(child);
|
||||||
if (firstOnly)
|
if (firstOnly)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (param.recursive !== false) {
|
if (param.recursive !== false) {
|
||||||
_getItems(child, match, childMatrix, param, firstOnly);
|
_getItems(child, options, childMatrix, param, firstOnly);
|
||||||
}
|
}
|
||||||
if (firstOnly && items.length > 0)
|
if (firstOnly && items.length > 0)
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -329,57 +329,6 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
selectedItems[i].setFullySelected(false);
|
selectedItems[i].setFullySelected(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform a hit-test on the items contained within the project at the
|
|
||||||
* location of the specified point.
|
|
||||||
*
|
|
||||||
* The options object allows you to control the specifics of the hit-test
|
|
||||||
* and may contain a combination of the following values:
|
|
||||||
*
|
|
||||||
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
|
||||||
* {Number} the tolerance of the hit-test
|
|
||||||
* @option options.class {Function} only hit-test again a certain item class
|
|
||||||
* and its sub-classes: {@values Group, Layer, Path, CompoundPath,
|
|
||||||
* Shape, Raster, SymbolItem, PointText, ...}
|
|
||||||
* @option [options.fill=true] {Boolean} hit-test the fill of items
|
|
||||||
* @option [options.stroke=true] {Boolean} hit-test the stroke of path
|
|
||||||
* items, taking into account the setting of stroke color and width
|
|
||||||
* @option [options.segments=true] {Boolean} hit-test for {@link
|
|
||||||
* Segment#point} of {@link Path} items
|
|
||||||
* @option options.curves {Boolean} hit-test the curves of path items,
|
|
||||||
* without taking the stroke color or width into account
|
|
||||||
* @option options.handles {Boolean} hit-test for the handles ({@link
|
|
||||||
* Segment#handleIn} / {@link Segment#handleOut}) of path segments.
|
|
||||||
* @option options.ends {Boolean} only hit-test for the first or last
|
|
||||||
* segment points of open path items
|
|
||||||
* @option options.bounds {Boolean} hit-test the corners and side-centers of
|
|
||||||
* the bounding rectangle of items ({@link Item#bounds})
|
|
||||||
* @option options.center {Boolean} hit-test the {@link Rectangle#center} of
|
|
||||||
* the bounding rectangle of items ({@link Item#bounds})
|
|
||||||
* @option options.guides {Boolean} hit-test items that have {@link
|
|
||||||
* Item#guide} set to `true`
|
|
||||||
* @option options.selected {Boolean} only hit selected items
|
|
||||||
*
|
|
||||||
* @param {Point} point the point where the hit-test should be performed
|
|
||||||
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
|
||||||
* tolerance: settings.hitTolerance }]
|
|
||||||
* @return {HitResult} a hit result object that contains more information
|
|
||||||
* about what exactly was hit or `null` if nothing was hit
|
|
||||||
*/
|
|
||||||
hitTest: function(/* point, options */) {
|
|
||||||
// We don't need to do this here, but it speeds up things since we won't
|
|
||||||
// repeatedly convert in Item#hitTest() then.
|
|
||||||
var point = Point.read(arguments),
|
|
||||||
options = HitResult.getOptions(Base.read(arguments)),
|
|
||||||
children = this._children;
|
|
||||||
// Loop backwards, so layers that get drawn last are tested first
|
|
||||||
for (var i = children.length - 1; i >= 0; i--) {
|
|
||||||
var res = children[i]._hitTest(point, options);
|
|
||||||
if (res) return res;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@grouptitle Hierarchy Operations}
|
* {@grouptitle Hierarchy Operations}
|
||||||
*
|
*
|
||||||
|
@ -436,7 +385,72 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@grouptitle Fetching and matching items}
|
* {@grouptitle Hit-testing, Fetching and Matching Items}
|
||||||
|
*
|
||||||
|
* Performs a hit-test on the items contained within the project at the
|
||||||
|
* location of the specified point.
|
||||||
|
*
|
||||||
|
* The options object allows you to control the specifics of the hit-test
|
||||||
|
* and may contain a combination of the following values:
|
||||||
|
*
|
||||||
|
* @name Project#hitTest
|
||||||
|
* @function
|
||||||
|
*
|
||||||
|
* @option [options.tolerance={@link PaperScope#settings}.hitTolerance]
|
||||||
|
* {Number} the tolerance of the hit-test
|
||||||
|
* @option options.class {Function} only hit-test again a certain item
|
||||||
|
* class and its sub-classes: {@values Group, Layer, Path,
|
||||||
|
* CompoundPath, Shape, Raster, SymbolItem, PointText, ...}
|
||||||
|
* @option options.match {Function} a match function to be called for each
|
||||||
|
* found hit result: Return `true` to return the result, `false` to keep
|
||||||
|
* searching
|
||||||
|
* @option [options.fill=true] {Boolean} hit-test the fill of items
|
||||||
|
* @option [options.stroke=true] {Boolean} hit-test the stroke of path
|
||||||
|
* items, taking into account the setting of stroke color and width
|
||||||
|
* @option [options.segments=true] {Boolean} hit-test for {@link
|
||||||
|
* Segment#point} of {@link Path} items
|
||||||
|
* @option options.curves {Boolean} hit-test the curves of path items,
|
||||||
|
* without taking the stroke color or width into account
|
||||||
|
* @option options.handles {Boolean} hit-test for the handles ({@link
|
||||||
|
* Segment#handleIn} / {@link Segment#handleOut}) of path segments.
|
||||||
|
* @option options.ends {Boolean} only hit-test for the first or last
|
||||||
|
* segment points of open path items
|
||||||
|
* @option options.bounds {Boolean} hit-test the corners and side-centers of
|
||||||
|
* the bounding rectangle of items ({@link Item#bounds})
|
||||||
|
* @option options.center {Boolean} hit-test the {@link Rectangle#center} of
|
||||||
|
* the bounding rectangle of items ({@link Item#bounds})
|
||||||
|
* @option options.guides {Boolean} hit-test items that have {@link
|
||||||
|
* Item#guide} set to `true`
|
||||||
|
* @option options.selected {Boolean} only hit selected items
|
||||||
|
*
|
||||||
|
* @param {Point} point the point where the hit-test should be performed
|
||||||
|
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||||
|
* tolerance: settings.hitTolerance }]
|
||||||
|
* @return {HitResult} a hit result object that contains more information
|
||||||
|
* about what exactly was hit or `null` if nothing was hit
|
||||||
|
*/
|
||||||
|
// NOTE: Implementation is in Item#hitTest()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a hit-test on the item and its children (if it is a {@link
|
||||||
|
* Group} or {@link Layer}) at the location of the specified point,
|
||||||
|
* returning all found hits.
|
||||||
|
*
|
||||||
|
* The options object allows you to control the specifics of the hit-
|
||||||
|
* test. See {@link #hitTest(point[, options])} for a list of all options.
|
||||||
|
*
|
||||||
|
* @name Item#hitTestAll
|
||||||
|
* @function
|
||||||
|
* @param {Point} point the point where the hit-test should be performed
|
||||||
|
* @param {Object} [options={ fill: true, stroke: true, segments: true,
|
||||||
|
* tolerance: settings.hitTolerance }]
|
||||||
|
* @return {HitResult[]} hit result objects for all hits, describing what
|
||||||
|
* exactly was hit or `null` if nothing was hit
|
||||||
|
* @see #hitTest(point[, options]);
|
||||||
|
*/
|
||||||
|
// NOTE: Implementation is in Item#hitTestAll()
|
||||||
|
|
||||||
|
/**
|
||||||
*
|
*
|
||||||
* Fetch items contained within the project whose properties match the
|
* Fetch items contained within the project whose properties match the
|
||||||
* criteria in the specified object.
|
* criteria in the specified object.
|
||||||
|
@ -448,21 +462,27 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
* matching does work for {@link Item#data}.
|
* matching does work for {@link Item#data}.
|
||||||
*
|
*
|
||||||
* Matching items against a rectangular area is also possible, by setting
|
* Matching items against a rectangular area is also possible, by setting
|
||||||
* either `match.inside` or `match.overlapping` to a rectangle describing
|
* either `options.inside` or `options.overlapping` to a rectangle
|
||||||
* the area in which the items either have to be fully or partly contained.
|
* describing the area in which the items either have to be fully or partly
|
||||||
|
* contained.
|
||||||
*
|
*
|
||||||
* @option [match.recursive=true] {Boolean} whether to loop recursively
|
* @option [options.recursive=true] {Boolean} whether to loop recursively
|
||||||
* through all children, or stop at the current level
|
* through all children, or stop at the current level
|
||||||
* @option match.match {Function} a match function to be called for each
|
* @option options.match {Function} a match function to be called for each
|
||||||
* item, allowing the definition of more flexible item checks that are
|
* item, allowing the definition of more flexible item checks that are
|
||||||
* not bound to properties. If no other match properties are defined,
|
* not bound to properties. If no other match properties are defined,
|
||||||
* this function can also be passed instead of the `match` object
|
* this function can also be passed instead of the `match` object
|
||||||
* @option match.class {Function} the constructor function of the item type
|
* @option options.class {Function} the constructor function of the item
|
||||||
* to match against
|
* type to match against
|
||||||
* @option match.inside {Rectangle} the rectangle in which the items need to
|
* @option options.inside {Rectangle} the rectangle in which the items need
|
||||||
* be fully contained
|
* to be fully contained
|
||||||
* @option match.overlapping {Rectangle} the rectangle with which the items
|
* @option options.overlapping {Rectangle} the rectangle with which the
|
||||||
* need to at least partly overlap
|
* items need to at least partly overlap
|
||||||
|
*
|
||||||
|
* @see Item#matches(options)
|
||||||
|
* @see Item#getItems(options)
|
||||||
|
* @param {Object|Function} options the criteria to match against
|
||||||
|
* @return {Item[]} the list of matching items contained in the project
|
||||||
*
|
*
|
||||||
* @example {@paperscript} // Fetch all selected path items:
|
* @example {@paperscript} // Fetch all selected path items:
|
||||||
* var path1 = new Path.Circle({
|
* var path1 = new Path.Circle({
|
||||||
|
@ -666,14 +686,9 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
||||||
* for (var i = 0; i < items.length; i++) {
|
* for (var i = 0; i < items.length; i++) {
|
||||||
* items[i].fillColor = 'red';
|
* items[i].fillColor = 'red';
|
||||||
* }
|
* }
|
||||||
*
|
|
||||||
* @see Item#matches(match)
|
|
||||||
* @see Item#getItems(match)
|
|
||||||
* @param {Object|Function} match the criteria to match against
|
|
||||||
* @return {Item[]} the list of matching items contained in the project
|
|
||||||
*/
|
*/
|
||||||
getItems: function(match) {
|
getItems: function(options) {
|
||||||
return Item._getItems(this, match);
|
return Item._getItems(this, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -684,13 +699,14 @@ 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}.
|
||||||
* See {@link #getItems(match)} for a selection of illustrated examples.
|
|
||||||
*
|
*
|
||||||
* @param {Object|Function} match the criteria to match against
|
* See {@link #getItems(options)} for a selection of illustrated examples.
|
||||||
|
*
|
||||||
|
* @param {Object|Function} options the criteria to match against
|
||||||
* @return {Item} the first item in the project matching the given criteria
|
* @return {Item} the first item in the project matching the given criteria
|
||||||
*/
|
*/
|
||||||
getItem: function(match) {
|
getItem: function(options) {
|
||||||
return Item._getItems(this, match, null, null, true)[0] || null;
|
return Item._getItems(this, options, null, null, true)[0] || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -341,7 +341,7 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
_hitTestSelf: function _hitTestSelf(point, options) {
|
_hitTestSelf: function _hitTestSelf(point, options, strokeMatrix) {
|
||||||
var hit = false,
|
var hit = false,
|
||||||
style = this._style;
|
style = this._style;
|
||||||
if (options.stroke && style.hasStroke()) {
|
if (options.stroke && style.hasStroke()) {
|
||||||
|
@ -350,8 +350,7 @@ new function() { // Scope for _contains() and _hitTestSelf() code.
|
||||||
strokeWidth = style.getStrokeWidth(),
|
strokeWidth = style.getStrokeWidth(),
|
||||||
strokePadding = options._tolerancePadding.add(
|
strokePadding = options._tolerancePadding.add(
|
||||||
Path._getStrokePadding(strokeWidth / 2,
|
Path._getStrokePadding(strokeWidth / 2,
|
||||||
!style.getStrokeScaling() &&
|
!style.getStrokeScaling() && strokeMatrix));
|
||||||
options._strokeMatrix));
|
|
||||||
if (type === 'rectangle') {
|
if (type === 'rectangle') {
|
||||||
var padding = strokePadding.multiply(2),
|
var padding = strokePadding.multiply(2),
|
||||||
center = getCornerCenter(this, point, padding);
|
center = getCornerCenter(this, point, padding);
|
||||||
|
|
|
@ -121,8 +121,8 @@ var SymbolItem = Item.extend(/** @lends SymbolItem# */{
|
||||||
options);
|
options);
|
||||||
},
|
},
|
||||||
|
|
||||||
_hitTestSelf: function(point, options) {
|
_hitTestSelf: function(point, options, strokeMatrix) {
|
||||||
var res = this._definition._item._hitTest(point, options);
|
var res = this._definition._item._hitTest(point, options, strokeMatrix);
|
||||||
// TODO: When the symbol's definition is a path, should hitResult
|
// TODO: When the symbol's definition is a path, should hitResult
|
||||||
// contain information like HitResult#curve?
|
// contain information like HitResult#curve?
|
||||||
if (res)
|
if (res)
|
||||||
|
|
|
@ -1250,7 +1250,7 @@ statics: /** @lends Curve */{
|
||||||
* @return {Number} the curvature of the curve at the given location
|
* @return {Number} the curvature of the curve at the given location
|
||||||
*/
|
*/
|
||||||
},
|
},
|
||||||
new function() { // // Scope to inject various curve evaluation methods
|
new function() { // Injection scope for various curve evaluation methods
|
||||||
var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent',
|
var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent',
|
||||||
'getWeightedNormal', 'getCurvature'];
|
'getWeightedNormal', 'getCurvature'];
|
||||||
return Base.each(methods,
|
return Base.each(methods,
|
||||||
|
|
|
@ -1548,7 +1548,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
|
|
||||||
toPath: '#clone',
|
toPath: '#clone',
|
||||||
|
|
||||||
_hitTestSelf: function(point, options) {
|
_hitTestSelf: function(point, options, strokeMatrix) {
|
||||||
var that = this,
|
var that = this,
|
||||||
style = this.getStyle(),
|
style = this.getStyle(),
|
||||||
segments = this._segments,
|
segments = this._segments,
|
||||||
|
@ -1579,7 +1579,7 @@ var Path = PathItem.extend(/** @lends Path# */{
|
||||||
// #strokeScaling into account through _getStrokeMatrix().
|
// #strokeScaling into account through _getStrokeMatrix().
|
||||||
strokePadding = tolerancePadding.add(
|
strokePadding = tolerancePadding.add(
|
||||||
Path._getStrokePadding(strokeRadius,
|
Path._getStrokePadding(strokeRadius,
|
||||||
!style.getStrokeScaling() && options._strokeMatrix));
|
!style.getStrokeScaling() && strokeMatrix));
|
||||||
} else {
|
} else {
|
||||||
join = cap = 'round';
|
join = cap = 'round';
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,8 @@ QUnit.jsDump.setParser('object', function (obj, stack) {
|
||||||
var compareProperties = function(actual, expected, properties, message, options) {
|
var compareProperties = function(actual, expected, properties, message, options) {
|
||||||
for (var i = 0, l = properties.length; i < l; i++) {
|
for (var i = 0, l = properties.length; i < l; i++) {
|
||||||
var key = properties[i];
|
var key = properties[i];
|
||||||
equals(actual[key], expected[key], message + '.' + key, options);
|
equals(actual[key], expected[key],
|
||||||
|
message + ' (#' + key + ')', options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -234,14 +235,14 @@ var compareItem = function(actual, expected, message, options, properties) {
|
||||||
} else {
|
} else {
|
||||||
if (options.cloned)
|
if (options.cloned)
|
||||||
QUnit.notStrictEqual(actual.id, expected.id,
|
QUnit.notStrictEqual(actual.id, expected.id,
|
||||||
'not ' + message + '.id');
|
message + ' (not #id)');
|
||||||
QUnit.strictEqual(actual.constructor, expected.constructor,
|
QUnit.strictEqual(actual.constructor, expected.constructor,
|
||||||
message + '.constructor');
|
message + ' (#constructor)');
|
||||||
// When item is cloned and has a name, the name will be versioned:
|
// When item is cloned and has a name, the name will be versioned:
|
||||||
equals(actual.name,
|
equals(actual.name,
|
||||||
options.cloned && expected.name
|
options.cloned && expected.name
|
||||||
? expected.name + ' 1' : expected.name,
|
? expected.name + ' 1' : expected.name,
|
||||||
message + '.name');
|
message + ' (#name)');
|
||||||
compareProperties(actual, expected, ['children', 'bounds', 'position',
|
compareProperties(actual, expected, ['children', 'bounds', 'position',
|
||||||
'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
|
'matrix', 'data', 'opacity', 'locked', 'visible', 'blendMode',
|
||||||
'selected', 'fullySelected', 'clipMask', 'guide'],
|
'selected', 'fullySelected', 'clipMask', 'guide'],
|
||||||
|
@ -255,7 +256,7 @@ var compareItem = function(actual, expected, message, options, properties) {
|
||||||
if (expected instanceof TextItem)
|
if (expected instanceof TextItem)
|
||||||
styles.push('fontSize', 'font', 'leading', 'justification');
|
styles.push('fontSize', 'font', 'leading', 'justification');
|
||||||
compareProperties(actual.style, expected.style, styles,
|
compareProperties(actual.style, expected.style, styles,
|
||||||
message + '.style', options);
|
message + ' (#style)', options);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -275,19 +276,19 @@ var comparators = {
|
||||||
// expected element, and compare values even if they may be inherited.
|
// expected element, and compare values even if they may be inherited.
|
||||||
// This is to handle styling values on SVGElement items more flexibly.
|
// This is to handle styling values on SVGElement items more flexibly.
|
||||||
equals(actual && actual.tagName, expected.tagName,
|
equals(actual && actual.tagName, expected.tagName,
|
||||||
(message || '') + '.tagName', options);
|
(message || '') + ' (#tagName)', options);
|
||||||
for (var i = 0; i < expected.attributes.length; i++) {
|
for (var i = 0; i < expected.attributes.length; i++) {
|
||||||
var attr = expected.attributes[i];
|
var attr = expected.attributes[i];
|
||||||
if (attr.specified) {
|
if (attr.specified) {
|
||||||
equals(actual && actual.getAttribute(attr.name), attr.value,
|
equals(actual && actual.getAttribute(attr.name), attr.value,
|
||||||
(message || '') + '.' + attr.name, options);
|
(message || '') + ' (#' + attr.name + ')', options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var i = 0; i < actual && actual.attributes.length; i++) {
|
for (var i = 0; i < actual && actual.attributes.length; i++) {
|
||||||
var attr = actual.attributes[i];
|
var attr = actual.attributes[i];
|
||||||
if (attr.specified) {
|
if (attr.specified) {
|
||||||
equals(attr.value, expected.getAttribute(attr.name)
|
equals(attr.value, expected.getAttribute(attr.name)
|
||||||
(message || '') + '.' + attr.name, options);
|
(message || '') + ' #(' + attr.name + ')', options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -304,7 +305,8 @@ var comparators = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Array: function(actual, expected, message, options) {
|
Array: function(actual, expected, message, options) {
|
||||||
QUnit.strictEqual(actual.length, expected.length, message + '.length');
|
QUnit.strictEqual(actual.length, expected.length, message
|
||||||
|
+ ' (#length)');
|
||||||
for (var i = 0, l = actual.length; i < l; i++) {
|
for (var i = 0, l = actual.length; i < l; i++) {
|
||||||
equals(actual[i], expected[i], (message || '') + '[' + i + ']',
|
equals(actual[i], expected[i], (message || '') + '[' + i + ']',
|
||||||
options);
|
options);
|
||||||
|
@ -312,15 +314,15 @@ var comparators = {
|
||||||
},
|
},
|
||||||
|
|
||||||
Point: function(actual, expected, message, options) {
|
Point: function(actual, expected, message, options) {
|
||||||
comparators.Number(actual.x, expected.x, message + '.x', options);
|
comparators.Number(actual.x, expected.x, message + ' (#x)', options);
|
||||||
comparators.Number(actual.y, expected.y, message + '.y', options);
|
comparators.Number(actual.y, expected.y, message + ' (#y)', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
Size: function(actual, expected, message, options) {
|
Size: function(actual, expected, message, options) {
|
||||||
comparators.Number(actual.width, expected.width, message + '.width',
|
comparators.Number(actual.width, expected.width,
|
||||||
options);
|
message + ' (#width)', options);
|
||||||
comparators.Number(actual.height, expected.height, message + '.height',
|
comparators.Number(actual.height, expected.height,
|
||||||
options);
|
message + ' (#height)', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
Rectangle: function(actual, expected, message, options) {
|
Rectangle: function(actual, expected, message, options) {
|
||||||
|
@ -334,10 +336,10 @@ var comparators = {
|
||||||
|
|
||||||
Color: function(actual, expected, message, options) {
|
Color: function(actual, expected, message, options) {
|
||||||
if (actual && expected) {
|
if (actual && expected) {
|
||||||
equals(actual.type, expected.type, message + '.type', options);
|
equals(actual.type, expected.type, message + ' (#type)', options);
|
||||||
// NOTE: This also compares gradients, with identity checks and all.
|
// NOTE: This also compares gradients, with identity checks and all.
|
||||||
equals(actual.components, expected.components,
|
equals(actual.components, expected.components,
|
||||||
message + '.components', options);
|
message + ' (#components)', options);
|
||||||
} else {
|
} else {
|
||||||
QUnit.strictEqual(actual, expected, message);
|
QUnit.strictEqual(actual, expected, message);
|
||||||
}
|
}
|
||||||
|
@ -351,7 +353,7 @@ var comparators = {
|
||||||
SegmentPoint: function(actual, expected, message, options) {
|
SegmentPoint: function(actual, expected, message, options) {
|
||||||
comparators.Point(actual, expected, message, options);
|
comparators.Point(actual, expected, message, options);
|
||||||
comparators.Boolean(actual.selected, expected.selected,
|
comparators.Boolean(actual.selected, expected.selected,
|
||||||
message + '.selected', options);
|
message + ' (#selected)', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
Item: compareItem,
|
Item: compareItem,
|
||||||
|
@ -367,7 +369,7 @@ var comparators = {
|
||||||
QUnit.push(sharedProject ? sameProject : !sameProject,
|
QUnit.push(sharedProject ? sameProject : !sameProject,
|
||||||
actual.project,
|
actual.project,
|
||||||
sharedProject ? expected.project : 'not ' + expected.project,
|
sharedProject ? expected.project : 'not ' + expected.project,
|
||||||
message + '.project');
|
message + ' (#project)');
|
||||||
},
|
},
|
||||||
|
|
||||||
Path: function(actual, expected, message, options) {
|
Path: function(actual, expected, message, options) {
|
||||||
|
@ -389,7 +391,7 @@ var comparators = {
|
||||||
comparePixels(actual, expected, message, options);
|
comparePixels(actual, expected, message, options);
|
||||||
} else {
|
} else {
|
||||||
equals(actual.toDataURL(), expected.toDataURL(),
|
equals(actual.toDataURL(), expected.toDataURL(),
|
||||||
message + '.toDataUrl()');
|
message + ' (#toDataUrl())');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -414,8 +416,8 @@ var comparators = {
|
||||||
},
|
},
|
||||||
|
|
||||||
SymbolDefinition: function(actual, expected, message, options) {
|
SymbolDefinition: function(actual, expected, message, options) {
|
||||||
equals(actual.definition, expected.definition, message + '.definition',
|
equals(actual.definition, expected.definition,
|
||||||
options);
|
message + ' (#definition)', options);
|
||||||
},
|
},
|
||||||
|
|
||||||
Project: function(actual, expected, message, options) {
|
Project: function(actual, expected, message, options) {
|
||||||
|
|
|
@ -727,5 +727,111 @@ test('hit-testing clipped items', function() {
|
||||||
}, true);
|
}, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('hit-testing with a match function', function() {
|
||||||
|
var point = new Point(100, 100),
|
||||||
|
red = new Color('red'),
|
||||||
|
green = new Color('green'),
|
||||||
|
blue = new Color('blue');
|
||||||
|
var c1 = new Path.Circle({
|
||||||
|
center: point,
|
||||||
|
radius: 50,
|
||||||
|
fillColor: red
|
||||||
|
});
|
||||||
|
var c2 = new Path.Circle({
|
||||||
|
center: point,
|
||||||
|
radius: 50,
|
||||||
|
fillColor: green
|
||||||
|
});
|
||||||
|
var c3 = new Path.Circle({
|
||||||
|
center: point,
|
||||||
|
radius: 50,
|
||||||
|
fillColor: blue
|
||||||
|
});
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTest(point, {
|
||||||
|
fill: true,
|
||||||
|
match: function(res) {
|
||||||
|
return res.item.fillColor == red;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result && result.item === c1;
|
||||||
|
}, true);
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTest(point, {
|
||||||
|
fill: true,
|
||||||
|
match: function(res) {
|
||||||
|
return res.item.fillColor == green;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result && result.item === c2;
|
||||||
|
}, true);
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTest(point, {
|
||||||
|
fill: true,
|
||||||
|
match: function(res) {
|
||||||
|
return res.item.fillColor == blue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result && result.item === c3;
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hit-testing for all items', function() {
|
||||||
|
var c1 = new Path.Circle({
|
||||||
|
center: [100, 100],
|
||||||
|
radius: 40,
|
||||||
|
fillColor: 'red'
|
||||||
|
});
|
||||||
|
var c2 = new Path.Circle({
|
||||||
|
center: [120, 120],
|
||||||
|
radius: 40,
|
||||||
|
fillColor: 'green'
|
||||||
|
});
|
||||||
|
var c3 = new Path.Circle({
|
||||||
|
center: [140, 140],
|
||||||
|
radius: 40,
|
||||||
|
fillColor: 'blue'
|
||||||
|
});
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([60, 60]);
|
||||||
|
return result.length === 0;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([80, 80]);
|
||||||
|
return result.length === 1 && result[0].item === c1;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([100, 100]);
|
||||||
|
return result.length === 2 && result[0].item === c2
|
||||||
|
&& result[1].item === c1;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([120, 120]);
|
||||||
|
return result.length === 3 && result[0].item === c3
|
||||||
|
&& result[1].item === c2
|
||||||
|
&& result[2].item === c1;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([140, 140]);
|
||||||
|
return result.length === 2 && result[0].item === c3
|
||||||
|
&& result[1].item === c2;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([160, 160]);
|
||||||
|
return result.length === 1 && result[0].item === c3;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
equals(function() {
|
||||||
|
var result = paper.project.hitTestAll([180, 180]);
|
||||||
|
return result.length === 0;
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
// TODO: project.hitTest(point, {type: AnItemType});
|
// TODO: project.hitTest(point, {type: AnItemType});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue