mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Unify the way item parents and projects as parents of top-level layers are handled as owners.
This automatically adds support for named children on project.layers, closing #491.
This commit is contained in:
parent
20f90bbee2
commit
406d26e884
4 changed files with 121 additions and 110 deletions
117
src/item/Item.js
117
src/item/Item.js
|
@ -90,8 +90,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
matrix = this._matrix = new Matrix(),
|
||||
// Allow setting another project than the currently active one.
|
||||
project = hasProps && props.project || paper.project;
|
||||
if (!internal)
|
||||
this._id = UID.get();
|
||||
this._id = internal ? null : UID.get();
|
||||
this._parent = this._index = null;
|
||||
// Inherit the applyMatrix setting from paper.settings.applyMatrix
|
||||
this._applyMatrix = this._canApplyMatrix && paper.settings.applyMatrix;
|
||||
// Handle matrix before everything else, to avoid issues with
|
||||
|
@ -100,31 +100,37 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
matrix.translate(point);
|
||||
matrix._owner = this;
|
||||
this._style = new Style(project._currentStyle, this, project);
|
||||
// If _project is already set, the item was already moved into the scene
|
||||
// graph. Used by Layer, where it's added to project.layers instead
|
||||
if (!this._project) {
|
||||
// Do not insert into DOM if it's an internal path, if props.insert
|
||||
// is false, or if the props are setting a different parent anyway.
|
||||
if (internal || hasProps && props.insert === false) {
|
||||
this._setProject(project);
|
||||
} else if (hasProps && props.parent) {
|
||||
this.setParent(props.parent);
|
||||
} else {
|
||||
// Create a new layer if there is no active one. This will
|
||||
// automatically make it the new activeLayer.
|
||||
(project._activeLayer || new Layer()).addChild(this);
|
||||
}
|
||||
// Do not add to the project if it's an internal path, if props.insert
|
||||
// is false, or if the props are setting a different parent anyway.
|
||||
if (internal || hasProps && props.insert === false) {
|
||||
this._setProject(project);
|
||||
} else if (hasProps && props.parent) {
|
||||
props.parent.addChild(this);
|
||||
} else {
|
||||
this._addToProject(project);
|
||||
}
|
||||
// Filter out Item.NO_INSERT before _set(), for performance reasons.
|
||||
if (hasProps && props !== Item.NO_INSERT)
|
||||
if (hasProps && props !== Item.NO_INSERT) {
|
||||
// Filter out insert, parent and project properties as these were
|
||||
// handled above.
|
||||
this._set(props, { insert: true, project: true, parent: true },
|
||||
// Don't check for plain object as that's done by hasProps.
|
||||
true);
|
||||
this._set(props,
|
||||
{ internal: true, insert: true, project: true, parent: true },
|
||||
// Don't check for plain object, as that's handled by hasProps.
|
||||
true);
|
||||
}
|
||||
return hasProps;
|
||||
},
|
||||
|
||||
/*
|
||||
* Private helper used in the constructor function to add the created item
|
||||
* to the project scene graph. Overridden in Layer.
|
||||
*/
|
||||
_addToProject: function(project) {
|
||||
// Create a new layer if there is no active one. This will
|
||||
// automatically make it the new activeLayer.
|
||||
(project._activeLayer || new Layer()).addChild(this);
|
||||
},
|
||||
|
||||
_events: Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick',
|
||||
'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'],
|
||||
function(name) {
|
||||
|
@ -323,10 +329,10 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
if (name === (+name) + '')
|
||||
throw new Error(
|
||||
'Names consisting only of numbers are not supported.');
|
||||
var parent = this._parent;
|
||||
if (name && parent) {
|
||||
var children = parent._children,
|
||||
namedChildren = parent._namedChildren;
|
||||
var owner = this._getOwner();
|
||||
if (name && owner) {
|
||||
var children = owner._children,
|
||||
namedChildren = owner._namedChildren;
|
||||
(namedChildren[name] = namedChildren[name] || []).push(this);
|
||||
// Only set this item if there isn't one under the same name already
|
||||
if (!(name in children))
|
||||
|
@ -1266,6 +1272,12 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
return item.addChild(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Private helper to return the owner, either the parent, or the project
|
||||
* for top-level layers. See Layer#_getOwner()
|
||||
*/
|
||||
_getOwner: '#getParent',
|
||||
|
||||
/**
|
||||
* The children items contained within this item. Items that define a
|
||||
* {@link #name} can also be accessed by name.
|
||||
|
@ -1361,7 +1373,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* @bean
|
||||
*/
|
||||
getNextSibling: function() {
|
||||
return this._parent && this._parent._children[this._index + 1] || null;
|
||||
var owner = this._getOwner();
|
||||
return owner && owner._children[this._index + 1] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1371,7 +1384,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* @bean
|
||||
*/
|
||||
getPreviousSibling: function() {
|
||||
return this._parent && this._parent._children[this._index - 1] || null;
|
||||
var owner = this._getOwner();
|
||||
return owner && owner._children[this._index - 1] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1527,13 +1541,13 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* or duplicates it within the same project. When passed an item,
|
||||
* copies the item into the specified item.
|
||||
*
|
||||
* @param {Project|Layer|Group|CompoundPath} item the item or project to
|
||||
* @param {Project|Layer|Group|CompoundPath} owner the item or project to
|
||||
* copy the item to
|
||||
* @return {Item} the new copy of the item
|
||||
*/
|
||||
copyTo: function(itemOrProject) {
|
||||
copyTo: function(owner) {
|
||||
// Pass false fo insert, since we're inserting at a specific location.
|
||||
return itemOrProject.addChild(this.clone(false));
|
||||
return owner.addChild(this.clone(false));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1950,7 +1964,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* @see #matches(match)
|
||||
*/
|
||||
getItems: function(match) {
|
||||
return Item._getItems(this._children, match, this._matrix);
|
||||
return Item._getItems(this, match, this._matrix);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1969,15 +1983,13 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* @see #getItems(match)
|
||||
*/
|
||||
getItem: function(match) {
|
||||
return Item._getItems(this._children, match, this._matrix, null, true)
|
||||
[0] || null;
|
||||
return Item._getItems(this, match, this._matrix, null, true)[0] || null;
|
||||
},
|
||||
|
||||
statics: {
|
||||
// NOTE: We pass children instead of item as first argument so the
|
||||
// method can be used for Project#layers as well in Project.
|
||||
_getItems: function _getItems(children, match, matrix, param,
|
||||
firstOnly) {
|
||||
_getItems: function _getItems(item, match, matrix, param, firstOnly) {
|
||||
if (!param) {
|
||||
// Set up a couple of "side-car" values for the recursive calls
|
||||
// of _getItems below, mainly related to the handling of
|
||||
|
@ -2007,7 +2019,8 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
});
|
||||
}
|
||||
}
|
||||
var items = param.items,
|
||||
var children = item._children,
|
||||
items = param.items,
|
||||
rect = param.rect;
|
||||
matrix = rect && (matrix || new Matrix());
|
||||
for (var i = 0, l = children && children.length; i < l; i++) {
|
||||
|
@ -2035,9 +2048,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
break;
|
||||
}
|
||||
if (param.recursive !== false) {
|
||||
_getItems(child._children, match,
|
||||
childMatrix, param,
|
||||
firstOnly);
|
||||
_getItems(child, match, childMatrix, param, firstOnly);
|
||||
}
|
||||
if (firstOnly && items.length > 0)
|
||||
break;
|
||||
|
@ -2078,9 +2089,7 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
// it as a child (this won't be successful on some classes, returning
|
||||
// null).
|
||||
var res = Base.importJSON(json, this);
|
||||
return res !== this
|
||||
? this.addChild(res)
|
||||
: res;
|
||||
return res !== this ? this.addChild(res) : res;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2273,20 +2282,16 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* Sends this item to the back of all other items within the same parent.
|
||||
*/
|
||||
sendToBack: function() {
|
||||
// If there is no parent and the item is a layer, delegate to project
|
||||
// instead.
|
||||
return (this._parent || this instanceof Layer && this._project)
|
||||
.insertChild(0, this);
|
||||
var owner = this._getOwner();
|
||||
return owner ? owner.insertChild(0, this) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Brings this item to the front of all other items within the same parent.
|
||||
*/
|
||||
bringToFront: function() {
|
||||
// If there is no parent and the item is a layer, delegate to project
|
||||
// instead.
|
||||
return (this._parent || this instanceof Layer && this._project)
|
||||
.addChild(this);
|
||||
var owner = this._getOwner();
|
||||
return owner ? owner.addChild(this) : null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2360,10 +2365,10 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
* Removes the item from its parent's named children list.
|
||||
*/
|
||||
_removeNamed: function() {
|
||||
var parent = this._parent;
|
||||
if (parent) {
|
||||
var children = parent._children,
|
||||
namedChildren = parent._namedChildren,
|
||||
var owner = this._getOwner();
|
||||
if (owner) {
|
||||
var children = owner._children,
|
||||
namedChildren = owner._namedChildren,
|
||||
name = this._name,
|
||||
namedArray = namedChildren[name],
|
||||
index = namedArray ? namedArray.indexOf(this) : -1;
|
||||
|
@ -2373,10 +2378,10 @@ var Item = Base.extend(Emitter, /** @lends Item# */{
|
|||
delete children[name];
|
||||
// Remove this entry
|
||||
namedArray.splice(index, 1);
|
||||
// If there are any items left in the named array, set
|
||||
// the last of them to be this.parent.children[this.name]
|
||||
// If there are any items left in the named array, set the first
|
||||
// of them to be children[this.name]
|
||||
if (namedArray.length) {
|
||||
children[name] = namedArray[namedArray.length - 1];
|
||||
children[name] = namedArray[0];
|
||||
} else {
|
||||
// Otherwise delete the empty array
|
||||
delete namedChildren[name];
|
||||
|
|
|
@ -62,19 +62,26 @@ var Layer = Group.extend(/** @lends Layer# */{
|
|||
* position: view.center
|
||||
* });
|
||||
*/
|
||||
initialize: function Layer(arg) {
|
||||
var props = Base.isPlainObject(arg)
|
||||
? new Base(arg) // clone so we can add insert = false
|
||||
: { children: Array.isArray(arg) ? arg : arguments },
|
||||
insert = props.insert;
|
||||
// Call the group constructor but don't insert yet!
|
||||
props.insert = false;
|
||||
Group.call(this, props);
|
||||
if (insert === undefined || insert) {
|
||||
this._project.addChild(this);
|
||||
// When inserted, also activate the layer by default.
|
||||
this.activate();
|
||||
}
|
||||
initialize: function Layer() {
|
||||
Group.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Private helper used in the constructor function to add the newly created
|
||||
* item to the project scene graph.
|
||||
*/
|
||||
_addToProject: function(project) {
|
||||
project.addChild(this);
|
||||
// When inserted, also activate the layer by default.
|
||||
this.activate();
|
||||
},
|
||||
|
||||
/**
|
||||
* Private helper to return the owner, either the parent, or the project
|
||||
* for top-level layers.
|
||||
*/
|
||||
_getOwner: function() {
|
||||
return this._parent || this._index != null && this._project;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -89,7 +96,7 @@ var Layer = Group.extend(/** @lends Layer# */{
|
|||
if (project._activeLayer === this)
|
||||
project._activeLayer = this.getNextSibling()
|
||||
|| this.getPreviousSibling();
|
||||
Base.splice(project.layers, null, this._index, 1);
|
||||
Base.splice(project._children, null, this._index, 1);
|
||||
this._installEvents(false);
|
||||
// Notify self of the insertion change. We only need this
|
||||
// notification if we're tracking changes for now.
|
||||
|
@ -107,16 +114,6 @@ var Layer = Group.extend(/** @lends Layer# */{
|
|||
return false;
|
||||
},
|
||||
|
||||
getNextSibling: function getNextSibling() {
|
||||
return this._parent ? getNextSibling.base.call(this)
|
||||
: this._project.layers[this._index + 1] || null;
|
||||
},
|
||||
|
||||
getPreviousSibling: function getPreviousSibling() {
|
||||
return this._parent ? getPreviousSibling.base.call(this)
|
||||
: this._project.layers[this._index - 1] || null;
|
||||
},
|
||||
|
||||
isInserted: function isInserted() {
|
||||
return this._parent ? isInserted.base.call(this) : this._index != null;
|
||||
},
|
||||
|
|
|
@ -52,7 +52,8 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
// so paper.project is set, as required by Layer and DoumentView
|
||||
// constructors.
|
||||
PaperScopeItem.call(this, true);
|
||||
this.layers = [];
|
||||
this._children = [];
|
||||
this._namedChildren = {};
|
||||
this._activeLayer = null;
|
||||
this.symbols = [];
|
||||
this._currentStyle = new Style(null, null, this);
|
||||
|
@ -76,7 +77,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
// project serialization later, but deserialization of a layers array
|
||||
// will always work.
|
||||
// Pass true for compact, so 'Project' does not get added as the class
|
||||
return Base.serialize(this.layers, options, true, dictionary);
|
||||
return Base.serialize(this._children, options, true, dictionary);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -92,8 +93,9 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* {@link Project#symbols}.
|
||||
*/
|
||||
clear: function() {
|
||||
for (var i = this.layers.length - 1; i >= 0; i--)
|
||||
this.layers[i].remove();
|
||||
var children = this._children;
|
||||
for (var i = children.length - 1; i >= 0; i--)
|
||||
children[i].remove();
|
||||
this.symbols = [];
|
||||
},
|
||||
|
||||
|
@ -103,7 +105,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* @return Boolean
|
||||
*/
|
||||
isEmpty: function() {
|
||||
return this.layers.length === 0;
|
||||
return this._children.length === 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -189,9 +191,14 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
*
|
||||
* The layers contained within the project.
|
||||
*
|
||||
* @name Project#layers
|
||||
* @bean
|
||||
* @type Layer[]
|
||||
*/
|
||||
getLayers: function() {
|
||||
return this._children;
|
||||
},
|
||||
|
||||
// TODO: Define #setLayers()?
|
||||
|
||||
/**
|
||||
* The layer which is currently active. New items will be created on this
|
||||
|
@ -237,7 +244,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
insertChild: function(index, item, _preserve) {
|
||||
if (item instanceof Layer) {
|
||||
item._remove(false, true);
|
||||
Base.splice(this.layers, [item], index, 0);
|
||||
Base.splice(this._children, [item], index, 0);
|
||||
item._setProject(this, true);
|
||||
// See Item#_remove() for an explanation of this:
|
||||
if (this._changes)
|
||||
|
@ -283,9 +290,9 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* Selects all items in the project.
|
||||
*/
|
||||
selectAll: function() {
|
||||
var layers = this.layers;
|
||||
for (var i = 0, l = layers.length; i < l; i++)
|
||||
layers[i].setFullySelected(true);
|
||||
var children = this._children;
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
children[i].setFullySelected(true);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -338,10 +345,11 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
// 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));
|
||||
options = HitResult.getOptions(Base.read(arguments)),
|
||||
children = this._children;
|
||||
// Loop backwards, so layers that get drawn last are tested first
|
||||
for (var i = this.layers.length - 1; i >= 0; i--) {
|
||||
var res = this.layers[i]._hitTest(point, options);
|
||||
for (var i = children.length - 1; i >= 0; i--) {
|
||||
var res = children[i]._hitTest(point, options);
|
||||
if (res) return res;
|
||||
}
|
||||
return null;
|
||||
|
@ -585,7 +593,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* @return {Item[]} the list of matching items contained in the project
|
||||
*/
|
||||
getItems: function(match) {
|
||||
return Item._getItems(this.layers, match);
|
||||
return Item._getItems(this, match);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -602,7 +610,7 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
* @return {Item} the first item in the project matching the given criteria
|
||||
*/
|
||||
getItem: function(match) {
|
||||
return Item._getItems(this.layers, match, null, null, true)[0] || null;
|
||||
return Item._getItems(this, match, null, null, true)[0] || null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -708,18 +716,19 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{
|
|||
ctx.save();
|
||||
matrix.applyToContext(ctx);
|
||||
// Use new Base() so we can use param.extend() to easily override values
|
||||
var param = new Base({
|
||||
offset: new Point(0, 0),
|
||||
pixelRatio: pixelRatio,
|
||||
viewMatrix: matrix.isIdentity() ? null : matrix,
|
||||
matrices: [new Matrix()], // Start with the identity matrix.
|
||||
// Tell the drawing routine that we want to keep _globalMatrix up to
|
||||
// date. Item#rasterize() and Raster#getAverageColor() should not
|
||||
// set this.
|
||||
updateMatrix: true
|
||||
});
|
||||
for (var i = 0, layers = this.layers, l = layers.length; i < l; i++)
|
||||
layers[i].draw(ctx, param);
|
||||
var children = this._children,
|
||||
param = new Base({
|
||||
offset: new Point(0, 0),
|
||||
pixelRatio: pixelRatio,
|
||||
viewMatrix: matrix.isIdentity() ? null : matrix,
|
||||
matrices: [new Matrix()], // Start with the identity matrix.
|
||||
// Tell the drawing routine that we want to keep _globalMatrix
|
||||
// up to date. Item#rasterize() and Raster#getAverageColor()
|
||||
// should not set this.
|
||||
updateMatrix: true
|
||||
});
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
children[i].draw(ctx, param);
|
||||
ctx.restore();
|
||||
|
||||
// Draw the selection of the selected items in the project:
|
||||
|
|
|
@ -417,7 +417,7 @@ new function() {
|
|||
Project.inject({
|
||||
exportSVG: function(options) {
|
||||
options = setOptions(options);
|
||||
var layers = this.layers,
|
||||
var children = this._children,
|
||||
view = this.getView(),
|
||||
size = view.getViewSize(),
|
||||
node = createElement('svg', {
|
||||
|
@ -436,8 +436,8 @@ new function() {
|
|||
if (!matrix.isIdentity())
|
||||
parent = node.appendChild(
|
||||
createElement('g', getTransform(matrix)));
|
||||
for (var i = 0, l = layers.length; i < l; i++)
|
||||
parent.appendChild(exportSVG(layers[i], options, true));
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
parent.appendChild(exportSVG(children[i], options, true));
|
||||
return exportDefinitions(node, options);
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue