2011-03-06 19:50:44 -05:00
|
|
|
/*
|
|
|
|
* Paper.js
|
|
|
|
*
|
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
2011-03-07 20:41:50 -05:00
|
|
|
* http://paperjs.org/
|
2011-03-06 19:50:44 -05:00
|
|
|
* http://scriptographer.org/
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
2011-03-06 19:50:44 -05:00
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* All rights reserved.
|
2011-03-06 19:50:44 -05:00
|
|
|
*/
|
|
|
|
|
2011-03-04 08:34:31 -05:00
|
|
|
var Item = this.Item = Base.extend({
|
2011-05-22 17:39:54 -04:00
|
|
|
/** @lends Item# */
|
2011-06-14 17:59:45 -04:00
|
|
|
|
2011-06-16 14:26:50 -04:00
|
|
|
/**
|
|
|
|
* @name Item
|
|
|
|
* @class The Item type allows you to access and modify the items in
|
|
|
|
* Paper.js projects. Its functionality is inherited by different project
|
|
|
|
* item types such as {@link Path}, {@link CompoundPath}, {@link Group},
|
|
|
|
* {@link Layer} and {@link Raster}. They each add a layer of functionality that
|
|
|
|
* is unique to their type, but share the underlying properties and functions
|
|
|
|
* that they inherit from Item.
|
|
|
|
*/
|
2011-02-07 13:28:09 -05:00
|
|
|
initialize: function() {
|
2011-05-20 20:05:22 -04:00
|
|
|
// If _project is already set, the item was already moved into the DOM
|
|
|
|
// hierarchy. Used by Layer, where it's added to project.layers instead
|
|
|
|
if (!this._project)
|
2011-06-17 10:58:22 -04:00
|
|
|
paper.project.activeLayer.addChild(this);
|
2011-05-16 14:21:36 -04:00
|
|
|
this._style = PathStyle.create(this);
|
2011-05-16 08:33:15 -04:00
|
|
|
this.setStyle(this._project.getCurrentStyle());
|
2011-02-11 12:36:03 -05:00
|
|
|
},
|
2011-03-04 15:57:16 -05:00
|
|
|
|
2011-05-07 09:57:20 -04:00
|
|
|
/**
|
|
|
|
* Private notifier that is called whenever a change occurs in this item or
|
|
|
|
* its sub-elements, such as Segments, Curves, PathStyles, etc.
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-06-19 17:21:14 -04:00
|
|
|
* @param {ChangeFlag} flags describes what exactly has changed.
|
2011-05-07 09:57:20 -04:00
|
|
|
*/
|
2011-05-07 08:39:17 -04:00
|
|
|
_changed: function(flags) {
|
2011-06-19 17:21:14 -04:00
|
|
|
if (flags & ChangeFlag.GEOMETRY) {
|
2011-06-19 13:07:53 -04:00
|
|
|
delete this._bounds;
|
2011-05-07 08:39:17 -04:00
|
|
|
delete this._position;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-05-14 17:38:27 -04:00
|
|
|
/**
|
|
|
|
* The unique id of the item.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-27 14:15:15 -04:00
|
|
|
* @type Number
|
2011-05-22 17:39:54 -04:00
|
|
|
* @bean
|
2011-05-14 17:38:27 -04:00
|
|
|
*/
|
|
|
|
getId: function() {
|
2011-05-16 14:44:46 -04:00
|
|
|
if (this._id == null)
|
2011-05-14 17:38:27 -04:00
|
|
|
this._id = Item._id = (Item._id || 0) + 1;
|
|
|
|
return this._id;
|
2011-05-15 06:32:09 -04:00
|
|
|
},
|
2011-05-15 13:53:09 -04:00
|
|
|
|
2011-05-15 13:12:27 -04:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The name of the item. If the item has a name, it can be accessed by name
|
|
|
|
* through its parent's children list.
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type String
|
|
|
|
* @bean
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
2011-06-03 09:34:10 -04:00
|
|
|
* // Set the name of the path:
|
2011-05-26 14:09:25 -04:00
|
|
|
* path.name = 'example';
|
2011-06-03 09:34:10 -04:00
|
|
|
*
|
2011-06-17 10:58:22 -04:00
|
|
|
* // Create a group and add path to it as a child:
|
2011-06-03 09:34:10 -04:00
|
|
|
* var group = new Group();
|
2011-06-17 10:58:22 -04:00
|
|
|
* group.addChild(path);
|
2011-06-03 09:34:10 -04:00
|
|
|
*
|
|
|
|
* // The path can be accessed by name:
|
2011-06-03 12:45:01 -04:00
|
|
|
* group.children['example'].fillColor = 'red';
|
2011-05-15 13:53:09 -04:00
|
|
|
*/
|
2011-05-15 13:12:27 -04:00
|
|
|
getName: function() {
|
2011-05-15 13:27:32 -04:00
|
|
|
return this._name;
|
2011-05-15 13:12:27 -04:00
|
|
|
},
|
2011-05-15 13:53:09 -04:00
|
|
|
|
2011-05-15 13:12:27 -04:00
|
|
|
setName: function(name) {
|
2011-06-17 11:32:47 -04:00
|
|
|
// Note: Don't check if the name has changed and bail out if it has not,
|
|
|
|
// because setName is used internally also to update internal structures
|
|
|
|
// when an item is moved from one parent to another.
|
|
|
|
|
|
|
|
// If the item already had a name, remove the reference to it from the
|
|
|
|
// parent's children object:
|
2011-06-17 08:29:47 -04:00
|
|
|
if (this._name)
|
|
|
|
this._removeFromNamed();
|
2011-06-17 11:32:47 -04:00
|
|
|
this._name = name || undefined;
|
2011-05-15 13:12:27 -04:00
|
|
|
if (name) {
|
2011-06-17 11:32:47 -04:00
|
|
|
var children = this._parent._children,
|
|
|
|
namedChildren = this._parent._namedChildren;
|
2011-05-15 13:27:32 -04:00
|
|
|
(namedChildren[name] = namedChildren[name] || []).push(this);
|
2011-05-15 13:12:27 -04:00
|
|
|
children[name] = this;
|
|
|
|
}
|
2011-06-19 17:21:14 -04:00
|
|
|
this._changed(ChangeFlag.ATTRIBUTE);
|
2011-05-15 13:12:27 -04:00
|
|
|
},
|
2011-05-16 14:35:09 -04:00
|
|
|
|
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The item's position within the project. This is the
|
2011-06-03 12:45:01 -04:00
|
|
|
* {@link Rectangle#center} of the item's {@link #bounds} rectangle.
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type Point
|
|
|
|
* @bean
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Changing the position of a path:
|
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* // Create a circle at position { x: 10, y: 10 }
|
|
|
|
* var circle = new Path.Circle(new Point(10, 10), 10);
|
|
|
|
* circle.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Move the circle to { x: 20, y: 20 }
|
|
|
|
* circle.position = new Point(20, 20);
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* // Move the circle 100 points to the right and 50 points down
|
|
|
|
* circle.position += new Point(100, 50);
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript split=true height=100}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Changing the x coordinate of an item's position:
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* // Create a circle at position { x: 20, y: 20 }
|
|
|
|
* var circle = new Path.Circle(new Point(20, 20), 10);
|
2011-05-26 14:09:25 -04:00
|
|
|
* circle.fillColor = 'red';
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* // Move the circle 100 points to the right
|
|
|
|
* circle.position.x += 100;
|
2011-05-26 14:09:25 -04:00
|
|
|
*/
|
|
|
|
getPosition: function() {
|
|
|
|
// Cache position value
|
2011-06-02 19:04:02 -04:00
|
|
|
var pos = this._position
|
|
|
|
|| (this._position = this.getBounds().getCenter());
|
|
|
|
// this._position is a LinkedPoint as well, so we can use _x and _y.
|
|
|
|
// Do not cache LinkedPoints directly, since we would not be able to
|
|
|
|
// use them to calculate the difference in #setPosition, as when it is
|
|
|
|
// modified, it would hold new values already and only then cause the
|
|
|
|
// calling of #setPosition.
|
|
|
|
return LinkedPoint.create(this, 'setPosition', pos._x, pos._y);
|
2011-05-16 14:35:09 -04:00
|
|
|
},
|
|
|
|
|
2011-05-26 14:09:25 -04:00
|
|
|
setPosition: function(point) {
|
2011-06-02 19:04:02 -04:00
|
|
|
this.translate(Point.read(arguments).subtract(this.getPosition()));
|
2011-05-16 14:35:09 -04:00
|
|
|
},
|
2011-05-14 17:38:27 -04:00
|
|
|
|
2011-02-12 11:43:51 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The path style of the item.
|
2011-05-30 13:42:17 -04:00
|
|
|
*
|
|
|
|
* @type PathStyle
|
|
|
|
* @bean
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Applying several styles to an item in one go, by passing an object
|
|
|
|
* // to its style property:
|
2011-06-03 12:45:01 -04:00
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 30);
|
2011-05-26 14:09:25 -04:00
|
|
|
* circle.style = {
|
2011-06-03 09:34:10 -04:00
|
|
|
* fillColor: 'blue',
|
|
|
|
* strokeColor: 'red',
|
2011-05-26 14:09:25 -04:00
|
|
|
* strokeWidth: 5
|
|
|
|
* };
|
2011-06-03 09:34:10 -04:00
|
|
|
*
|
|
|
|
* @example {@paperscript split=true height=100}
|
|
|
|
* // Copying the style of another item:
|
|
|
|
* var path = new Path.Circle(new Point(50, 50), 30);
|
|
|
|
* path.fillColor = 'red';
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* var path2 = new Path.Circle(new Point(180, 50), 20);
|
2011-06-03 09:34:10 -04:00
|
|
|
* // Copy the path style of path:
|
|
|
|
* path2.style = path.style;
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Applying the same style object to multiple items:
|
|
|
|
* var myStyle = {
|
|
|
|
* fillColor: 'red',
|
|
|
|
* strokeColor: 'blue',
|
|
|
|
* strokeWidth: 4
|
|
|
|
* };
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* var path = new Path.Circle(new Point(80, 50), 30);
|
2011-06-03 09:34:10 -04:00
|
|
|
* path.style = myStyle;
|
|
|
|
*
|
|
|
|
* var path2 = new Path.Circle(new Point(150, 50), 20);
|
|
|
|
* path2.style = myStyle;
|
2011-02-12 11:43:51 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
getStyle: function() {
|
|
|
|
return this._style;
|
|
|
|
},
|
|
|
|
|
|
|
|
setStyle: function(style) {
|
|
|
|
this._style.initialize(style);
|
2011-06-18 19:40:34 -04:00
|
|
|
}
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-06-17 13:53:34 -04:00
|
|
|
}, new function() { // Injection scope to produce getter setters for properties
|
|
|
|
// We need setters because we want to call _changed() if a property was
|
|
|
|
// modified.
|
|
|
|
return Base.each(['locked', 'visible', 'blendMode', 'opacity'],
|
|
|
|
function(name) {
|
|
|
|
var part = Base.capitalize(name),
|
|
|
|
name = '_' + name;
|
|
|
|
this['get' + part] = function() {
|
|
|
|
return this[name];
|
|
|
|
};
|
|
|
|
this['set' + part] = function(value) {
|
|
|
|
if (value != this[name]) {
|
|
|
|
this[name] = value;
|
|
|
|
// #locked does not change appearance, all others do:
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(name === '_locked'
|
2011-06-19 17:21:14 -04:00
|
|
|
? ChangeFlag.ATTRIBUTE : Change.ATTRIBUTE);
|
2011-06-17 13:53:34 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}, {});
|
|
|
|
}, {
|
|
|
|
/** @lends Item# */
|
2011-06-14 10:35:39 -04:00
|
|
|
|
2011-06-17 13:53:34 -04:00
|
|
|
// Note: These properties have their getter / setters produced in the
|
|
|
|
// injection scope above.
|
2011-05-26 14:09:25 -04:00
|
|
|
|
2011-02-11 12:36:03 -05:00
|
|
|
/**
|
|
|
|
* Specifies whether the item is locked.
|
|
|
|
*
|
2011-06-17 13:53:34 -04:00
|
|
|
* @name Item#locked
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type Boolean
|
2011-05-22 17:39:54 -04:00
|
|
|
* @default false
|
2011-05-26 14:09:25 -04:00
|
|
|
* @ignore
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
2011-06-17 13:53:34 -04:00
|
|
|
_locked: false,
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-11 12:36:03 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* Specifies whether the item is visible. When set to {@code false}, the
|
|
|
|
* item won't be drawn.
|
2011-02-11 12:36:03 -05:00
|
|
|
*
|
2011-06-17 13:53:34 -04:00
|
|
|
* @name Item#visible
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type Boolean
|
|
|
|
* @default true
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Hiding an item:
|
2011-05-26 14:09:25 -04:00
|
|
|
* var path = new Path.Circle(new Point(50, 50), 20);
|
|
|
|
* path.fillColor = 'red';
|
2011-06-03 09:34:10 -04:00
|
|
|
*
|
|
|
|
* // Hide the path:
|
|
|
|
* path.visible = false;
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
2011-06-17 13:53:34 -04:00
|
|
|
_visible: true,
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-26 14:09:25 -04:00
|
|
|
/**
|
|
|
|
* The blend mode of the item.
|
|
|
|
*
|
2011-06-17 13:53:34 -04:00
|
|
|
* @name Item#blendMode
|
2011-06-10 07:34:20 -04:00
|
|
|
* @type String('normal', 'multiply', 'screen', 'overlay', 'soft-light',
|
|
|
|
* 'hard-light', 'color-dodge', 'color-burn', 'darken', 'lighten',
|
|
|
|
* 'difference', 'exclusion', 'hue', 'saturation', 'luminosity', 'color',
|
|
|
|
* 'add', 'subtract', 'average', 'pin-light', 'negation')
|
2011-05-30 13:42:17 -04:00
|
|
|
* @default 'normal'
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Setting an item's blend mode:
|
|
|
|
*
|
|
|
|
* // Create a white rectangle in the background
|
|
|
|
* // with the same dimensions as the view:
|
|
|
|
* var background = new Path.Rectangle(view.bounds);
|
|
|
|
* background.fillColor = 'white';
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 35);
|
2011-05-26 14:09:25 -04:00
|
|
|
* circle.fillColor = 'red';
|
2011-06-03 09:34:10 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* var circle2 = new Path.Circle(new Point(120, 50), 35);
|
2011-06-03 09:34:10 -04:00
|
|
|
* circle2.fillColor = 'blue';
|
|
|
|
*
|
|
|
|
* // Set the blend mode of circle2:
|
|
|
|
* circle2.blendMode = 'multiply';
|
2011-05-26 14:09:25 -04:00
|
|
|
*/
|
2011-06-17 13:53:34 -04:00
|
|
|
_blendMode: 'normal',
|
2011-05-26 14:09:25 -04:00
|
|
|
|
|
|
|
/**
|
2011-05-31 08:28:42 -04:00
|
|
|
* The opacity of the item as a value between {@code 0} and {@code 1}.
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-17 13:53:34 -04:00
|
|
|
* @name Item#opacity
|
|
|
|
* @type Number
|
|
|
|
* @default 1
|
|
|
|
*
|
2011-06-03 09:34:10 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Making an item 50% transparent:
|
2011-06-03 12:45:01 -04:00
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 35);
|
2011-05-26 14:09:25 -04:00
|
|
|
* circle.fillColor = 'red';
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* var circle2 = new Path.Circle(new Point(120, 50), 35);
|
2011-06-03 09:34:10 -04:00
|
|
|
* circle2.style = {
|
|
|
|
* fillColor: 'blue',
|
|
|
|
* strokeColor: 'green',
|
|
|
|
* strokeWidth: 10
|
|
|
|
* };
|
|
|
|
*
|
|
|
|
* // Make circle2 50% transparent:
|
|
|
|
* circle2.opacity = 0.5;
|
2011-06-17 13:53:34 -04:00
|
|
|
*/
|
|
|
|
_opacity: 1,
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies whether an item is selected and will also return {@code true}
|
|
|
|
* if the item is partially selected (groups with some selected or partially
|
|
|
|
* selected paths).
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-17 13:53:34 -04:00
|
|
|
* Paper.js draws the visual outlines of selected items on top of your
|
|
|
|
* project. This can be useful for debugging, as it allows you to see the
|
|
|
|
* construction of paths, position of path curves, individual segment points
|
|
|
|
* and bounding boxes of symbol and raster items.
|
|
|
|
*
|
|
|
|
* @type Boolean
|
|
|
|
* @default false
|
|
|
|
* @bean
|
|
|
|
* @see Project#selectedItems
|
|
|
|
* @see Segment#selected
|
|
|
|
* @see Point#selected
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Selecting an item:
|
|
|
|
* var path = new Path.Circle(new Size(80, 50), 35);
|
|
|
|
* path.selected = true; // Select the path
|
|
|
|
*/
|
|
|
|
isSelected: function() {
|
|
|
|
if (this._children) {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++)
|
|
|
|
if (this._children[i].isSelected())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return this._selected;
|
|
|
|
},
|
|
|
|
|
|
|
|
setSelected: function(selected) {
|
|
|
|
if (this._children) {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++) {
|
|
|
|
this._children[i].setSelected(selected);
|
|
|
|
}
|
|
|
|
} else if ((selected = !!selected) != this._selected) {
|
|
|
|
this._selected = selected;
|
|
|
|
this._project._updateSelection(this);
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.ATTRIBUTE);
|
2011-06-17 13:53:34 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_selected: false,
|
|
|
|
|
|
|
|
// TODO: isFullySelected / setFullySelected
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies whether the item defines a clip mask. This can only be set on
|
|
|
|
* paths, compound paths, and text frame objects, and only if the item is
|
|
|
|
* already contained within a clipping group.
|
|
|
|
*
|
|
|
|
* @type Boolean
|
|
|
|
* @default false
|
|
|
|
* @bean
|
2011-05-26 14:09:25 -04:00
|
|
|
*/
|
2011-06-17 13:53:34 -04:00
|
|
|
isClipMask: function() {
|
|
|
|
return this._clipMask;
|
|
|
|
},
|
|
|
|
|
|
|
|
setClipMask: function(clipMask) {
|
2011-06-19 17:36:04 -04:00
|
|
|
// On-the-fly conversion to boolean:
|
|
|
|
if (this._clipMask != (clipMask = !!clipMask)) {
|
|
|
|
this._clipMask = clipMask;
|
|
|
|
if (clipMask) {
|
|
|
|
this.setFillColor(null);
|
|
|
|
this.setStrokeColor(null);
|
|
|
|
}
|
|
|
|
this._changed(Change.ATTRIBUTE);
|
|
|
|
// Tell the parent the clipping mask has changed
|
|
|
|
if (this._parent)
|
|
|
|
this._parent._changed(ChangeFlag.CLIPPING);
|
2011-06-17 13:53:34 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_clipMask: false,
|
2011-05-26 14:09:25 -04:00
|
|
|
|
2011-05-14 13:07:10 -04:00
|
|
|
// TODO: get/setIsolated (print specific feature)
|
2011-02-24 11:42:32 -05:00
|
|
|
// TODO: get/setKnockout (print specific feature)
|
2011-05-26 07:04:47 -04:00
|
|
|
// TODO: get/setAlphaIsShape
|
2011-02-24 11:42:32 -05:00
|
|
|
// TODO: get/setData
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-14 13:07:10 -04:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* {@grouptitle Project Hierarchy}
|
|
|
|
* The project that this item belongs to.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @type Project
|
2011-05-22 17:39:54 -04:00
|
|
|
* @bean
|
2011-05-14 13:07:10 -04:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
getProject: function() {
|
|
|
|
return this._project;
|
|
|
|
},
|
|
|
|
|
|
|
|
_setProject: function(project) {
|
|
|
|
if (this._project != project) {
|
|
|
|
this._project = project;
|
|
|
|
if (this._children) {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++) {
|
|
|
|
this._children[i]._setProject(project);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-05-14 13:07:10 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
// TODO: #getLayer()
|
|
|
|
|
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The item that this item is contained within.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type Item
|
|
|
|
* @bean
|
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @example
|
|
|
|
* var path = new Path();
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* // New items are placed in the active layer:
|
|
|
|
* console.log(path.parent == project.activeLayer); // true
|
|
|
|
*
|
|
|
|
* var group = new Group();
|
2011-06-17 10:58:22 -04:00
|
|
|
* group.addChild(path);
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* // Now the parent of the path has become the group:
|
|
|
|
* console.log(path.parent == group); // true
|
2011-05-14 13:07:10 -04:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
getParent: function() {
|
|
|
|
return this._parent;
|
2011-05-14 13:07:10 -04:00
|
|
|
},
|
|
|
|
|
2011-06-03 12:45:01 -04:00
|
|
|
// DOCS: add comment to Item#children about not playing around with the
|
2011-06-17 10:58:22 -04:00
|
|
|
// array directly - use addChild etc instead.
|
2011-05-14 13:07:10 -04:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The children items contained within this item. Items that define a
|
|
|
|
* {@link #name} can also be accessed by name.
|
2011-05-30 13:42:17 -04:00
|
|
|
*
|
2011-06-17 10:59:24 -04:00
|
|
|
* <b>Please note:</b> The children array should not be modified directly
|
|
|
|
* using array functions. To remove single items from the children list, use
|
|
|
|
* {@link Item#remove()}, to remove all items from the children list, use
|
|
|
|
* {@link Item#removeChildren()}. To add items to the children list, use
|
2011-06-18 11:40:50 -04:00
|
|
|
* {@link Item#addChild(item)} or {@link Item#insertChild(index,item)}.
|
2011-06-17 10:59:24 -04:00
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @type Item[]
|
|
|
|
* @bean
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Accessing items in the children array:
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
*
|
|
|
|
* // Create a group and move the path into it:
|
2011-05-26 14:09:25 -04:00
|
|
|
* var group = new Group();
|
2011-06-17 10:58:22 -04:00
|
|
|
* group.addChild(path);
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Access the path through the group's children array:
|
|
|
|
* group.children[0].fillColor = 'red';
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Accessing children by name:
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
* // Set the name of the path:
|
2011-05-26 14:09:25 -04:00
|
|
|
* path.name = 'example';
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* // Create a group and move the path into it:
|
|
|
|
* var group = new Group();
|
2011-06-17 10:58:22 -04:00
|
|
|
* group.addChild(path);
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* // The path can be accessed by name:
|
|
|
|
* group.children['example'].fillColor = 'orange';
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Passing an array of items to item.children:
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
*
|
|
|
|
* var group = new Group();
|
|
|
|
* group.children = [path];
|
|
|
|
*
|
|
|
|
* // The path is the first child of the group:
|
|
|
|
* group.firstChild.fillColor = 'green';
|
2011-05-14 13:07:10 -04:00
|
|
|
*/
|
|
|
|
getChildren: function() {
|
|
|
|
return this._children;
|
|
|
|
},
|
|
|
|
|
2011-05-16 15:15:16 -04:00
|
|
|
setChildren: function(items) {
|
2011-05-16 14:34:57 -04:00
|
|
|
this.removeChildren();
|
2011-06-19 16:48:36 -04:00
|
|
|
this.addChildren(items);
|
2011-05-16 14:34:57 -04:00
|
|
|
},
|
|
|
|
|
2011-05-16 14:35:09 -04:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The first item contained within this item. This is a shortcut for
|
|
|
|
* accessing {@code item.children[0]}.
|
2011-05-16 14:35:09 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @type Item
|
|
|
|
* @bean
|
2011-05-16 14:35:09 -04:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
getFirstChild: function() {
|
|
|
|
return this._children && this._children[0] || null;
|
2011-05-16 14:35:09 -04:00
|
|
|
},
|
2011-05-14 13:07:10 -04:00
|
|
|
|
2011-02-24 13:31:07 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* The last item contained within this item.This is a shortcut for
|
|
|
|
* accessing {@code item.children[item.children.length - 1]}.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @type Item
|
|
|
|
* @bean
|
2011-02-24 13:31:07 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
getLastChild: function() {
|
|
|
|
return this._children && this._children[this._children.length - 1]
|
|
|
|
|| null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The next item on the same level as this item.
|
|
|
|
*
|
|
|
|
* @type Item
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getNextSibling: function() {
|
|
|
|
return this._parent && this._parent._children[this._index + 1] || null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The previous item on the same level as this item.
|
|
|
|
*
|
|
|
|
* @type Item
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getPreviousSibling: function() {
|
|
|
|
return this._parent && this._parent._children[this._index - 1] || null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The index of this item within the list of its parent's children.
|
|
|
|
*
|
2011-05-27 14:15:15 -04:00
|
|
|
* @type Number
|
2011-05-26 14:09:25 -04:00
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getIndex: function() {
|
|
|
|
return this._index;
|
|
|
|
},
|
|
|
|
|
2011-02-11 12:36:03 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* Clones the item within the same project and places the copy above the
|
|
|
|
* item.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @return {Item} the newly cloned item
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Cloning items:
|
|
|
|
* var circle = new Path.Circle(new Point(50, 50), 10);
|
|
|
|
* circle.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Make 20 copies of the circle:
|
|
|
|
* for (var i = 0; i < 20; i++) {
|
|
|
|
* var copy = circle.clone();
|
|
|
|
*
|
2011-06-10 07:34:58 -04:00
|
|
|
* // Distribute the copies horizontally, so we can see them:
|
2011-06-03 12:45:01 -04:00
|
|
|
* copy.position.x += i * copy.bounds.width;
|
|
|
|
* }
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
clone: function() {
|
|
|
|
return this._clone(new this.constructor());
|
|
|
|
},
|
|
|
|
|
|
|
|
_clone: function(copy) {
|
|
|
|
// Copy over style
|
|
|
|
copy.setStyle(this._style);
|
|
|
|
// If this item has children, clone and append each of them:
|
|
|
|
if (this._children) {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++)
|
2011-06-17 10:58:22 -04:00
|
|
|
copy.addChild(this._children[i].clone());
|
2011-05-26 14:09:25 -04:00
|
|
|
}
|
|
|
|
// Only copy over these fields if they are actually defined in 'this'
|
|
|
|
// TODO: Consider moving this to Base once it's useful in more than one
|
|
|
|
// place
|
2011-06-17 13:53:34 -04:00
|
|
|
var keys = ['_locked', '_visible', '_opacity', '_blendMode',
|
|
|
|
'_clipMask', '_selected'];
|
2011-05-26 14:09:25 -04:00
|
|
|
for (var i = 0, l = keys.length; i < l; i++) {
|
|
|
|
var key = keys[i];
|
|
|
|
if (this.hasOwnProperty(key))
|
|
|
|
copy[key] = this[key];
|
|
|
|
}
|
2011-06-17 10:58:22 -04:00
|
|
|
// Insert the clone above the original, at the same position.
|
|
|
|
copy.insertAbove(this);
|
2011-05-26 14:09:25 -04:00
|
|
|
// Only set name once the copy is moved, to avoid setting and unsettting
|
|
|
|
// name related structures.
|
|
|
|
if (this._name)
|
|
|
|
copy.setName(this._name);
|
|
|
|
return copy;
|
2011-02-11 12:36:03 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-11 12:36:03 -05:00
|
|
|
/**
|
2011-06-17 12:35:26 -04:00
|
|
|
* When passed a project, copies the item to the project,
|
|
|
|
* 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
|
|
|
|
* copy the item to
|
|
|
|
* @return {Item} the new copy of the item
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
2011-06-17 12:35:26 -04:00
|
|
|
copyTo: function(itemOrProject) {
|
|
|
|
var copy = this.clone();
|
|
|
|
if (itemOrProject.layers) {
|
|
|
|
itemOrProject.activeLayer.addChild(copy);
|
|
|
|
} else {
|
|
|
|
itemOrProject.addChild(copy);
|
2011-05-26 14:09:25 -04:00
|
|
|
}
|
2011-06-17 12:35:26 -04:00
|
|
|
return copy;
|
2011-02-11 12:36:03 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* Rasterizes the item into a newly created Raster object. The item itself
|
|
|
|
* is not removed after rasterization.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
* @param {Number} [resolution=72] the resolution of the raster in dpi
|
2011-05-26 14:09:25 -04:00
|
|
|
* @return {Raster} the newly created raster item
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Rasterizing an item:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 5);
|
|
|
|
* circle.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Create a rasterized version of the path:
|
|
|
|
* var raster = circle.rasterize();
|
|
|
|
*
|
|
|
|
* // Move it 100pt to the right:
|
|
|
|
* raster.position.x += 100;
|
|
|
|
*
|
|
|
|
* // Scale the path and the raster by 300%, so we can compare them:
|
|
|
|
* circle.scale(5);
|
|
|
|
* raster.scale(5);
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
rasterize: function(resolution) {
|
|
|
|
// TODO: why would we want to pass a size to rasterize? Seems to produce
|
|
|
|
// weird results on Scriptographer. Also we can't use antialiasing, since
|
|
|
|
// Canvas doesn't support it yet. Project colorMode is also out of the
|
|
|
|
// question for now.
|
|
|
|
var bounds = this.getStrokeBounds(),
|
|
|
|
scale = (resolution || 72) / 72,
|
|
|
|
canvas = CanvasProvider.getCanvas(bounds.getSize().multiply(scale)),
|
|
|
|
ctx = canvas.getContext('2d'),
|
|
|
|
matrix = new Matrix().scale(scale).translate(-bounds.x, -bounds.y);
|
|
|
|
matrix.applyToContext(ctx);
|
|
|
|
this.draw(ctx, {});
|
|
|
|
var raster = new Raster(canvas);
|
|
|
|
raster.setPosition(this.getPosition());
|
|
|
|
raster.scale(1 / scale);
|
|
|
|
return raster;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-06-17 12:35:26 -04:00
|
|
|
* {@grouptitle Hierarchy Operations}
|
2011-06-19 16:48:36 -04:00
|
|
|
* Adds the specified item as a child of this item at the end of the
|
2011-06-17 12:35:26 -04:00
|
|
|
* its children list. You can use this function for groups, compound
|
|
|
|
* paths and layers.
|
|
|
|
*
|
2011-06-19 16:48:36 -04:00
|
|
|
* @param {Item} item The item to be added as a child
|
|
|
|
*/
|
2011-06-17 12:35:26 -04:00
|
|
|
addChild: function(item) {
|
|
|
|
return this.insertChild(undefined, item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-06-19 16:48:36 -04:00
|
|
|
* Inserts the specified item as a child of this item at the specified
|
2011-06-17 12:35:26 -04:00
|
|
|
* index in its {@link #children} list. You can use this function for
|
|
|
|
* groups, compound paths and layers.
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-17 12:35:26 -04:00
|
|
|
* @param {Number} index
|
2011-06-19 16:48:36 -04:00
|
|
|
* @param {Item} item The item to be appended as a child
|
2011-05-26 14:09:25 -04:00
|
|
|
*/
|
2011-06-17 12:35:26 -04:00
|
|
|
insertChild: function(index, item) {
|
|
|
|
if (this._children) {
|
2011-06-19 12:32:43 -04:00
|
|
|
item._remove(false, true);
|
2011-06-17 12:35:26 -04:00
|
|
|
Base.splice(this._children, [item], index, 0);
|
|
|
|
item._parent = this;
|
|
|
|
item._setProject(this._project);
|
|
|
|
if (item._name)
|
|
|
|
item.setName(item._name);
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.HIERARCHY);
|
2011-06-17 12:35:26 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2011-06-19 16:48:36 -04:00
|
|
|
/**
|
|
|
|
* Adds the specified items as children of this item at the end of the
|
|
|
|
* its children list. You can use this function for groups, compound
|
|
|
|
* paths and layers.
|
|
|
|
*
|
|
|
|
* @param {item[]} items The items to be added as children
|
|
|
|
*/
|
|
|
|
addChildren: function(items) {
|
|
|
|
for (var i = 0, l = items && items.length; i < l; i++)
|
|
|
|
this.insertChild(undefined, items[i]);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts the specified items as children of this item at the specified
|
|
|
|
* index in its {@link #children} list. You can use this function for
|
|
|
|
* groups, compound paths and layers.
|
|
|
|
*
|
|
|
|
* @param {Number} index
|
|
|
|
* @param {Item[]} items The items to be appended as children
|
|
|
|
*/
|
|
|
|
insertChildren: function(index, items) {
|
|
|
|
for (var i = 0, l = items && items.length; i < l; i++) {
|
|
|
|
if (this.insertChild(index, items[i]))
|
|
|
|
index++;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-06-17 12:35:26 -04:00
|
|
|
/**
|
|
|
|
* Inserts this item above the specified item.
|
|
|
|
*
|
|
|
|
* @param {Item} item The item above which it should be moved
|
|
|
|
* @return {Boolean} {@true it was inserted}
|
|
|
|
*/
|
|
|
|
insertAbove: function(item) {
|
|
|
|
return item._parent && item._parent.insertChild(
|
|
|
|
item._index + 1, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts this item below the specified item.
|
|
|
|
*
|
|
|
|
* @param {Item} item The item above which it should be moved
|
|
|
|
* @return {Boolean} {@true it was inserted}
|
|
|
|
*/
|
|
|
|
insertBelow: function(item) {
|
|
|
|
return item._parent && item._parent.insertChild(
|
|
|
|
item._index - 1, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2011-06-19 16:48:36 -04:00
|
|
|
* Inserts the specified item as a child of this item by appending it to
|
2011-06-17 12:35:26 -04:00
|
|
|
* the list of children and moving it above all other children. You can
|
|
|
|
* use this function for groups, compound paths and layers.
|
|
|
|
*
|
2011-06-19 16:48:36 -04:00
|
|
|
* @param {Item} item The item to be appended as a child
|
2011-06-17 12:35:26 -04:00
|
|
|
* @deprecated use {@link #addChild(item)} instead.
|
|
|
|
*/
|
|
|
|
appendTop: function(item) {
|
|
|
|
return this.addChild(item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts the specified item as a child of this item by appending it to
|
|
|
|
* the list of children and moving it below all other children. You can
|
|
|
|
* use this function for groups, compound paths and layers.
|
|
|
|
*
|
2011-06-19 16:48:36 -04:00
|
|
|
* @param {Item} item The item to be appended as a child
|
2011-06-17 12:35:26 -04:00
|
|
|
* @deprecated use {@link #insertChild(index, item)} instead.
|
|
|
|
*/
|
|
|
|
appendBottom: function(item) {
|
|
|
|
return this.insertChild(0, item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Moves this item above the specified item.
|
|
|
|
*
|
|
|
|
* @param {Item} item The item above which it should be moved
|
|
|
|
* @return {Boolean} {@true it was moved}
|
|
|
|
* @deprecated use {@link #insertAbove(item)} instead.
|
|
|
|
*/
|
|
|
|
moveAbove: function(item) {
|
|
|
|
return this.insertAbove(item);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Moves the item below the specified item.
|
|
|
|
*
|
|
|
|
* @param {Item} item the item below which it should be moved
|
|
|
|
* @return {Boolean} {@true it was moved}
|
|
|
|
* @deprecated use {@link #insertBelow(item)} instead.
|
|
|
|
*/
|
|
|
|
moveBelow: function(item) {
|
|
|
|
return this.insertBelow(item);
|
|
|
|
},
|
|
|
|
|
2011-06-17 13:04:00 -04:00
|
|
|
/**
|
|
|
|
* Removes the item from its parent's named children list.
|
|
|
|
*/
|
2011-06-17 12:35:26 -04:00
|
|
|
_removeFromNamed: function() {
|
|
|
|
var children = this._parent._children,
|
|
|
|
namedChildren = this._parent._namedChildren,
|
|
|
|
name = this._name,
|
|
|
|
namedArray = namedChildren[name],
|
|
|
|
index = namedArray ? namedArray.indexOf(this) : -1;
|
|
|
|
if (index == -1)
|
|
|
|
return;
|
|
|
|
// Remove the named reference
|
|
|
|
if (children[name] == this)
|
|
|
|
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 (namedArray.length) {
|
|
|
|
children[name] = namedArray[namedArray.length - 1];
|
|
|
|
} else {
|
|
|
|
// Otherwise delete the empty array
|
|
|
|
delete namedChildren[name];
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the item from its parent's children list.
|
|
|
|
*/
|
2011-06-19 12:32:43 -04:00
|
|
|
_remove: function(deselect, notify) {
|
2011-06-17 12:35:26 -04:00
|
|
|
if (this._parent) {
|
2011-06-19 12:30:47 -04:00
|
|
|
if (deselect)
|
|
|
|
this.setSelected(false);
|
2011-06-17 12:35:26 -04:00
|
|
|
if (this._name)
|
|
|
|
this._removeFromNamed();
|
2011-06-19 11:08:51 -04:00
|
|
|
Base.splice(this._parent._children, null, this._index, 1);
|
2011-06-19 12:30:47 -04:00
|
|
|
// Notify parent of changed hierarchy
|
|
|
|
if (notify)
|
2011-06-19 17:20:28 -04:00
|
|
|
this._parent._changed(Change.HIERARCHY);
|
2011-06-17 12:35:26 -04:00
|
|
|
this._parent = null;
|
2011-06-19 11:08:51 -04:00
|
|
|
return true;
|
2011-06-17 12:35:26 -04:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the item from the project. If the item has children, they are also
|
|
|
|
* removed.
|
|
|
|
*
|
|
|
|
* @return {Boolean} {@true the item was removed}
|
|
|
|
*/
|
|
|
|
remove: function() {
|
2011-06-19 12:32:43 -04:00
|
|
|
return this._remove(true, true);
|
2011-06-17 12:35:26 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes all of the item's {@link #children} (if any).
|
|
|
|
*
|
2011-06-17 12:55:30 -04:00
|
|
|
* @name Item#removeChildren
|
|
|
|
* @function
|
|
|
|
* @return {Array} an array containing the removed items
|
2011-06-17 12:35:26 -04:00
|
|
|
*/
|
2011-06-17 12:55:30 -04:00
|
|
|
/**
|
|
|
|
* Removes all of the item's {@link #children} (if any).
|
|
|
|
*
|
|
|
|
* @return {Array} an array containing the removed items
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Removes the children from the specified {@code from} index to the
|
|
|
|
* {@code to} index from the parent's {@link #children} array.
|
|
|
|
*
|
|
|
|
* @param {Number} from the beginning index, inclusive
|
|
|
|
* @param {Number} [to=children.length] the ending index, exclusive
|
|
|
|
* @return {Array} an array containing the removed items
|
|
|
|
*/
|
|
|
|
removeChildren: function(from, to) {
|
|
|
|
if (!this._children)
|
|
|
|
return null;
|
|
|
|
from = from || 0;
|
|
|
|
to = Base.pick(to, this._children.length);
|
|
|
|
var removed = this._children.splice(from, to - from);
|
|
|
|
for (var i = removed.length - 1; i >= 0; i--)
|
2011-06-19 12:32:43 -04:00
|
|
|
removed[i]._remove(true, false);
|
2011-06-19 12:30:47 -04:00
|
|
|
if (removed.length > 0)
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.HIERARCHY);
|
2011-06-17 12:35:26 -04:00
|
|
|
return removed;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reverses the order of the item's children
|
|
|
|
*/
|
|
|
|
reverseChildren: function() {
|
|
|
|
if (this._children) {
|
|
|
|
this._children.reverse();
|
2011-06-19 16:49:26 -04:00
|
|
|
// Adjust inidces
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++)
|
2011-06-17 12:35:26 -04:00
|
|
|
this._children[i]._index = i;
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.HIERARCHY);
|
2011-06-17 12:35:26 -04:00
|
|
|
}
|
2011-02-11 12:36:03 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-26 14:09:25 -04:00
|
|
|
// TODO: Item#isEditable is currently ignored in the documentation, as
|
|
|
|
// locking an item currently has no effect
|
2011-02-12 11:43:51 -05:00
|
|
|
/**
|
2011-06-17 12:35:26 -04:00
|
|
|
* {@grouptitle Tests}
|
2011-02-12 11:43:51 -05:00
|
|
|
* Checks whether the item is editable.
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true when neither the item, nor its parents are
|
2011-05-27 12:43:27 -04:00
|
|
|
* locked or hidden}
|
2011-05-26 14:09:25 -04:00
|
|
|
* @ignore
|
2011-02-12 11:43:51 -05:00
|
|
|
*/
|
|
|
|
isEditable: function() {
|
2011-05-29 14:22:59 -04:00
|
|
|
var item = this;
|
|
|
|
while (item) {
|
2011-06-17 13:53:34 -04:00
|
|
|
if (!item._visible || item._locked)
|
2011-02-12 11:43:51 -05:00
|
|
|
return false;
|
2011-05-29 14:22:59 -04:00
|
|
|
item = item._parent;
|
2011-02-12 11:43:51 -05:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
/**
|
|
|
|
* Checks whether the item is valid, i.e. it hasn't been removed.
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true the item is valid}
|
2011-02-24 11:42:32 -05:00
|
|
|
*/
|
|
|
|
// TODO: isValid / checkValid
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-29 14:54:43 -04:00
|
|
|
/**
|
|
|
|
* Returns -1 if 'this' is above 'item', 1 if below, 0 if their order is not
|
|
|
|
* defined in such a way, e.g. if one is a descendant of the other.
|
|
|
|
*/
|
|
|
|
_getOrder: function(item) {
|
2011-05-30 10:17:44 -04:00
|
|
|
// Private method that produces a list of anchestors, starting with the
|
|
|
|
// root and ending with the actual element as the last entry.
|
2011-05-29 14:54:43 -04:00
|
|
|
function getList(item) {
|
|
|
|
var list = [];
|
|
|
|
do {
|
|
|
|
list.unshift(item);
|
|
|
|
} while (item = item._parent)
|
|
|
|
return list;
|
|
|
|
}
|
|
|
|
var list1 = getList(this),
|
|
|
|
list2 = getList(item);
|
|
|
|
for (var i = 0, l = Math.min(list1.length, list2.length); i < l; i++) {
|
|
|
|
if (list1[i] != list2[i]) {
|
|
|
|
// Found the position in the parents list where the two start
|
|
|
|
// to differ. Look at who's above who.
|
|
|
|
return list1[i]._index < list2[i]._index ? 1 : -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
},
|
|
|
|
|
2011-06-17 12:35:26 -04:00
|
|
|
/**
|
|
|
|
* {@grouptitle Hierarchy Tests}
|
|
|
|
*
|
|
|
|
* Checks if the item contains any children items.
|
|
|
|
*
|
|
|
|
* @return {Boolean} {@true it has one or more children}
|
|
|
|
*/
|
|
|
|
hasChildren: function() {
|
|
|
|
return this._children && this._children.length > 0;
|
|
|
|
},
|
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
/**
|
2011-05-22 17:39:54 -04:00
|
|
|
* Checks if this item is above the specified item in the stacking order
|
|
|
|
* of the project.
|
2011-02-24 11:42:32 -05:00
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @param {Item} item The item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if it is above the specified item}
|
2011-02-24 11:42:32 -05:00
|
|
|
*/
|
2011-05-29 14:54:43 -04:00
|
|
|
isAbove: function(item) {
|
|
|
|
return this._getOrder(item) == -1;
|
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
/**
|
|
|
|
* Checks if the item is below the specified item in the stacking order of
|
2011-05-16 08:33:15 -04:00
|
|
|
* the project.
|
2011-02-24 11:42:32 -05:00
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @param {Item} item The item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if it is below the specified item}
|
2011-02-24 11:42:32 -05:00
|
|
|
*/
|
2011-05-29 14:54:43 -04:00
|
|
|
isBelow: function(item) {
|
|
|
|
return this._getOrder(item) == 1;
|
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-22 17:39:54 -04:00
|
|
|
/**
|
|
|
|
* Checks whether the specified item is the parent of the item.
|
|
|
|
*
|
|
|
|
* @param {Item} item The item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if it is the parent of the item}
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
2011-04-11 13:42:03 -04:00
|
|
|
isParent: function(item) {
|
2011-05-14 12:56:14 -04:00
|
|
|
return this._parent == item;
|
2011-04-11 13:42:03 -04:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-22 17:39:54 -04:00
|
|
|
/**
|
|
|
|
* Checks whether the specified item is a child of the item.
|
|
|
|
*
|
|
|
|
* @param {Item} item The item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true it is a child of the item}
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
2011-02-11 12:36:03 -05:00
|
|
|
isChild: function(item) {
|
2011-05-28 16:06:30 -04:00
|
|
|
return item && item._parent == this;
|
2011-02-11 12:36:03 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-11 12:36:03 -05:00
|
|
|
/**
|
|
|
|
* Checks if the item is contained within the specified item.
|
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @param {Item} item The item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if it is inside the specified item}
|
2011-02-11 12:36:03 -05:00
|
|
|
*/
|
|
|
|
isDescendant: function(item) {
|
2011-04-27 10:16:05 -04:00
|
|
|
var parent = this;
|
2011-05-14 12:56:14 -04:00
|
|
|
while (parent = parent._parent) {
|
2011-02-13 11:26:24 -05:00
|
|
|
if (parent == item)
|
2011-02-12 10:41:57 -05:00
|
|
|
return true;
|
2011-02-11 12:36:03 -05:00
|
|
|
}
|
2011-02-12 10:41:57 -05:00
|
|
|
return false;
|
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-12 10:41:57 -05:00
|
|
|
/**
|
|
|
|
* Checks if the item is an ancestor of the specified item.
|
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @param {Item} item the item to check against
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if the item is an ancestor of the specified
|
2011-05-27 12:43:27 -04:00
|
|
|
* item}
|
2011-02-12 10:41:57 -05:00
|
|
|
*/
|
|
|
|
isAncestor: function(item) {
|
2011-05-28 16:09:03 -04:00
|
|
|
return item ? item.isDescendant(this) : false;
|
2011-02-13 20:05:16 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
/**
|
|
|
|
* Checks whether the item is grouped with the specified item.
|
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @param {Item} item
|
2011-05-30 13:42:17 -04:00
|
|
|
* @return {Boolean} {@true if the items are grouped together}
|
2011-02-24 11:42:32 -05:00
|
|
|
*/
|
2011-02-24 12:09:48 -05:00
|
|
|
isGroupedWith: function(item) {
|
2011-05-14 12:56:14 -04:00
|
|
|
var parent = this._parent;
|
2011-04-12 08:37:52 -04:00
|
|
|
while (parent) {
|
2011-05-14 12:56:14 -04:00
|
|
|
// Find group parents. Check for parent._parent, since don't want
|
2011-02-24 12:09:48 -05:00
|
|
|
// top level layers, because they also inherit from Group
|
2011-05-14 12:56:14 -04:00
|
|
|
if (parent._parent
|
2011-02-24 12:09:48 -05:00
|
|
|
&& (parent instanceof Group || parent instanceof CompoundPath)
|
|
|
|
&& item.isDescendant(parent))
|
|
|
|
return true;
|
|
|
|
// Keep walking up otherwise
|
2011-05-14 12:56:14 -04:00
|
|
|
parent = parent._parent;
|
2011-02-24 12:09:48 -05:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
2011-05-22 17:39:54 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* {@grouptitle Bounding Rectangles}
|
|
|
|
*
|
|
|
|
* The bounding rectangle of the item excluding stroke width.
|
|
|
|
* @type Rectangle
|
|
|
|
* @bean
|
|
|
|
*/
|
2011-02-13 20:05:16 -05:00
|
|
|
getBounds: function() {
|
2011-04-28 06:33:03 -04:00
|
|
|
return this._getBounds(false);
|
|
|
|
},
|
2011-05-22 17:39:54 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including stroke width.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @type Rectangle
|
|
|
|
* @bean
|
|
|
|
*/
|
|
|
|
getStrokeBounds: function() {
|
|
|
|
return this._getBounds(true);
|
|
|
|
},
|
|
|
|
|
2011-04-28 06:33:03 -04:00
|
|
|
_getBounds: function(includeStroke) {
|
2011-05-14 13:07:10 -04:00
|
|
|
var children = this._children;
|
2011-04-28 05:04:36 -04:00
|
|
|
// TODO: What to return if nothing is defined, e.g. empty Groups?
|
|
|
|
// Scriptographer behaves weirdly then too.
|
2011-06-19 12:47:20 -04:00
|
|
|
if (!children || children.length == 0)
|
|
|
|
return new Rectangle();
|
|
|
|
var x1 = Infinity,
|
|
|
|
x2 = -Infinity,
|
|
|
|
y1 = x1,
|
|
|
|
y2 = x2,
|
|
|
|
getBounds = includeStroke ? 'getStrokeBounds' : 'getBounds';
|
|
|
|
for (var i = 0, l = children.length; i < l; i++) {
|
|
|
|
var child = children[i];
|
|
|
|
if (child._visible) {
|
|
|
|
var rect = child[getBounds]();
|
|
|
|
x1 = Math.min(rect.x, x1);
|
|
|
|
y1 = Math.min(rect.y, y1);
|
|
|
|
x2 = Math.max(rect.x + rect.width, x2);
|
|
|
|
y2 = Math.max(rect.y + rect.height, y2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return includeStroke
|
2011-06-19 13:07:53 -04:00
|
|
|
? Rectangle.create(x1, y1, x2 - x1, y2 - y1)
|
|
|
|
: LinkedRectangle.create(this, 'setBounds',
|
|
|
|
x1, y1, x2 - x1, y2 - y1);
|
2011-02-13 20:05:16 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
setBounds: function(rect) {
|
|
|
|
rect = Rectangle.read(arguments);
|
2011-03-13 13:31:00 -04:00
|
|
|
var bounds = this.getBounds(),
|
|
|
|
matrix = new Matrix(),
|
2011-06-18 21:28:27 -04:00
|
|
|
center = rect.getCenter();
|
2011-02-13 20:05:16 -05:00
|
|
|
// Read this from bottom to top:
|
|
|
|
// Translate to new center:
|
|
|
|
matrix.translate(center);
|
|
|
|
// Scale to new Size, if size changes and avoid divisions by 0:
|
|
|
|
if (rect.width != bounds.width || rect.height != bounds.height) {
|
|
|
|
matrix.scale(
|
|
|
|
bounds.width != 0 ? rect.width / bounds.width : 1,
|
|
|
|
bounds.height != 0 ? rect.height / bounds.height : 1);
|
|
|
|
}
|
|
|
|
// Translate to center:
|
2011-06-18 21:28:27 -04:00
|
|
|
center = bounds.getCenter();
|
2011-02-13 20:05:16 -05:00
|
|
|
matrix.translate(-center.x, -center.y);
|
|
|
|
// Now execute the transformation:
|
2011-02-13 20:14:43 -05:00
|
|
|
this.transform(matrix);
|
2011-02-13 20:05:16 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
/**
|
|
|
|
* The bounding rectangle of the item including stroke width and controls.
|
|
|
|
*/
|
|
|
|
// TODO: getControlBounds
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-05-27 14:05:51 -04:00
|
|
|
/**
|
|
|
|
* {@grouptitle Stroke Style}
|
|
|
|
*
|
|
|
|
* The color of the stroke.
|
|
|
|
*
|
2011-06-02 07:58:56 -04:00
|
|
|
* @property
|
|
|
|
* @name Item#strokeColor
|
|
|
|
* @type RGBColor|HSBColor|GrayColor
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Setting the stroke color of a path:
|
2011-05-31 10:48:48 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
* // with a radius of 35:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 35);
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
2011-05-31 10:48:48 -04:00
|
|
|
* // Set its stroke color to RGB red:
|
2011-05-30 13:42:17 -04:00
|
|
|
* circle.strokeColor = new RGBColor(1, 0, 0);
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The width of the stroke.
|
|
|
|
*
|
2011-05-31 10:48:48 -04:00
|
|
|
* @property
|
|
|
|
* @name Item#strokeWidth
|
|
|
|
* @type Number
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-31 10:48:48 -04:00
|
|
|
* // Setting an item's stroke width:
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
* // with a radius of 35:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 35);
|
|
|
|
*
|
|
|
|
* // Set its stroke color to black:
|
|
|
|
* circle.strokeColor = 'black';
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Set its stroke width to 10:
|
|
|
|
* circle.strokeWidth = 10;
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2011-06-02 07:58:56 -04:00
|
|
|
* The shape to be used at the end of open {@link Path} items, when they
|
|
|
|
* have a stroke.
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
2011-05-31 10:48:48 -04:00
|
|
|
* @property
|
|
|
|
* @name Item#strokeCap
|
|
|
|
* @default 'butt'
|
|
|
|
* @type String('round', 'square', 'butt')
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // A look at the different stroke caps:
|
2011-05-31 10:48:48 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* var line = new Path(new Point(80, 50), new Point(420, 50));
|
2011-05-31 10:48:48 -04:00
|
|
|
* line.strokeColor = 'black';
|
2011-06-03 12:45:01 -04:00
|
|
|
* line.strokeWidth = 20;
|
|
|
|
*
|
|
|
|
* // Select the path, so we can see where the stroke is formed:
|
|
|
|
* line.selected = true;
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
|
|
|
* // Set the stroke cap of the line to be round:
|
|
|
|
* line.strokeCap = 'round';
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* // Copy the path and set its stroke cap to be square:
|
|
|
|
* var line2 = line.clone();
|
|
|
|
* line2.position.y += 50;
|
|
|
|
* line2.strokeCap = 'square';
|
|
|
|
*
|
|
|
|
* // Make another copy and set its stroke cap to be butt:
|
|
|
|
* var line2 = line.clone();
|
|
|
|
* line2.position.y += 100;
|
|
|
|
* line2.strokeCap = 'butt';
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2011-06-02 08:17:47 -04:00
|
|
|
* The shape to be used at the corners of paths when they have a stroke.
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
|
|
|
* @property
|
|
|
|
* @name Item#strokeJoin
|
2011-05-31 10:48:48 -04:00
|
|
|
* @default 'miter'
|
2011-05-29 08:49:51 -04:00
|
|
|
* @type String ('miter', 'round', 'bevel')
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* @example {@paperscript height=120}
|
|
|
|
* // A look at the different stroke joins:
|
|
|
|
* var path = new Path();
|
|
|
|
* path.add(new Point(80, 100));
|
|
|
|
* path.add(new Point(120, 40));
|
|
|
|
* path.add(new Point(160, 100));
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
* path.strokeWidth = 20;
|
|
|
|
*
|
|
|
|
* // Select the path, so we can see where the stroke is formed:
|
|
|
|
* path.selected = true;
|
|
|
|
*
|
|
|
|
* var path2 = path.clone();
|
|
|
|
* path2.position.x += path2.bounds.width * 1.5;
|
|
|
|
* path2.strokeJoin = 'round';
|
|
|
|
*
|
|
|
|
* var path3 = path2.clone();
|
|
|
|
* path3.position.x += path3.bounds.width * 1.5;
|
|
|
|
* path3.strokeJoin = 'bevel';
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
2011-06-17 15:04:32 -04:00
|
|
|
/**
|
|
|
|
* The dash offset of the stroke.
|
|
|
|
*
|
|
|
|
* @property
|
|
|
|
* @name Item#dashOffset
|
|
|
|
* @default 0
|
|
|
|
* @type Number
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Specifies an array containing the dash and gap lengths of the stroke.
|
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 40);
|
|
|
|
* path.strokeWidth = 2;
|
|
|
|
* path.strokeColor = 'black';
|
|
|
|
*
|
|
|
|
* // Set the dashed stroke to [10pt dash, 4pt gap]:
|
|
|
|
* path.dashArray = [10, 4];
|
|
|
|
*
|
|
|
|
* @property
|
|
|
|
* @name Item#dashArray
|
|
|
|
* @default []
|
|
|
|
* @type Array
|
|
|
|
*/
|
2011-05-27 14:05:51 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The miter limit of the stroke.
|
2011-06-02 07:58:56 -04:00
|
|
|
* When two line segments meet at a sharp angle and miter joins have been
|
2011-06-04 09:32:28 -04:00
|
|
|
* specified for {@link Item#strokeJoin}, it is possible for the miter to
|
|
|
|
* extend far beyond the {@link Item#strokeWidth} of the path. The
|
|
|
|
* miterLimit imposes a limit on the ratio of the miter length to the
|
|
|
|
* {@link Item#strokeWidth}.
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
|
|
|
* @property
|
2011-05-31 10:48:48 -04:00
|
|
|
* @default 10
|
2011-05-27 14:05:51 -04:00
|
|
|
* @name Item#miterLimit
|
2011-05-27 14:15:15 -04:00
|
|
|
* @type Number
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@grouptitle Fill Style}
|
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* The fill color of the item.
|
|
|
|
*
|
|
|
|
* @property
|
|
|
|
* @name Item#fillColor
|
|
|
|
* @type RGBColor|HSBColor|GrayColor
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Setting the fill color of a path to red:
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
* // with a radius of 35:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 35);
|
2011-05-27 14:05:51 -04:00
|
|
|
*
|
|
|
|
* // Set the fill color of the circle to RGB red:
|
2011-06-03 12:45:01 -04:00
|
|
|
* circle.fillColor = new RGBColor(1, 0, 0);
|
2011-05-27 14:05:51 -04:00
|
|
|
*/
|
|
|
|
|
2011-05-22 17:39:54 -04:00
|
|
|
// DOCS: document the different arguments that this function can receive.
|
2011-02-13 20:05:16 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* {@grouptitle Transform Functions}
|
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* Scales the item by the given value from its center point, or optionally
|
2011-06-03 12:45:01 -04:00
|
|
|
* from a supplied point.
|
2011-02-13 20:05:16 -05:00
|
|
|
*
|
2011-05-30 13:42:17 -04:00
|
|
|
* @name Item#scale
|
|
|
|
* @function
|
|
|
|
* @param {Number} scale the scale factor
|
2011-06-17 13:04:00 -04:00
|
|
|
* @param {Point} [center={@link Item#position}]
|
2011-05-30 13:42:17 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Scaling an item from its center point:
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
* // with a radius of 20:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 20);
|
|
|
|
* circle.fillColor = 'red';
|
2011-05-22 17:39:54 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Scale the path by 150% from its center point
|
|
|
|
* circle.scale(1.5);
|
2011-05-22 17:39:54 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
2011-05-30 13:42:17 -04:00
|
|
|
* // Scaling an item from a specific point:
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 80, y: 50 }
|
|
|
|
* // with a radius of 20:
|
|
|
|
* var circle = new Path.Circle(new Point(80, 50), 20);
|
|
|
|
* circle.fillColor = 'red';
|
2011-02-13 20:05:16 -05:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Scale the path 150% from its bottom left corner
|
|
|
|
* circle.scale(1.5, circle.bounds.bottomLeft);
|
2011-02-13 20:05:16 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
/**
|
|
|
|
* Scales the item by the given values from its center point, or optionally
|
2011-06-03 12:45:01 -04:00
|
|
|
* from a supplied point.
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-16 14:26:50 -04:00
|
|
|
* @name Item#scale
|
|
|
|
* @function
|
2011-05-30 13:42:17 -04:00
|
|
|
* @param {Number} sx the horizontal scale factor
|
|
|
|
* @param {Number} sy the vertical scale factor
|
2011-06-17 13:04:00 -04:00
|
|
|
* @param {Point} [center={@link Item#position}]
|
2011-05-30 13:42:17 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript}
|
|
|
|
* // Scaling an item horizontally by 300%:
|
2011-05-26 14:09:25 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Create a circle shaped path at { x: 100, y: 50 }
|
|
|
|
* // with a radius of 20:
|
|
|
|
* var circle = new Path.Circle(new Point(100, 50), 20);
|
|
|
|
* circle.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Scale the path horizontally by 300%
|
|
|
|
* circle.scale(3, 1);
|
2011-05-26 14:09:25 -04:00
|
|
|
*/
|
2011-02-13 20:05:16 -05:00
|
|
|
scale: function(sx, sy /* | scale */, center) {
|
2011-02-15 18:23:40 -05:00
|
|
|
// See Matrix#scale for explanation of this:
|
2011-04-28 08:23:17 -04:00
|
|
|
if (arguments.length < 2 || typeof sy === 'object') {
|
2011-02-15 18:23:40 -05:00
|
|
|
center = sy;
|
|
|
|
sy = sx;
|
|
|
|
}
|
2011-03-06 06:42:08 -05:00
|
|
|
return this.transform(new Matrix().scale(sx, sy,
|
2011-03-06 16:12:11 -05:00
|
|
|
center || this.getPosition()));
|
2011-02-13 20:05:16 -05:00
|
|
|
},
|
|
|
|
|
2011-05-26 14:09:25 -04:00
|
|
|
/**
|
|
|
|
* Translates (moves) the item by the given offset point.
|
|
|
|
*
|
|
|
|
* @param {Point} delta the offset to translate the item by
|
|
|
|
*/
|
|
|
|
translate: function(delta) {
|
|
|
|
var mx = new Matrix();
|
|
|
|
return this.transform(mx.translate.apply(mx, arguments));
|
|
|
|
},
|
|
|
|
|
2011-02-13 20:05:16 -05:00
|
|
|
/**
|
|
|
|
* Rotates the item by a given angle around the given point.
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* Angles are oriented clockwise and measured in degrees.
|
2011-02-13 20:05:16 -05:00
|
|
|
*
|
2011-05-27 15:21:49 -04:00
|
|
|
* @param {Number} angle the rotation angle
|
2011-06-17 13:04:00 -04:00
|
|
|
* @param {Point} [center={@link Item#position}]
|
2011-05-22 17:39:54 -04:00
|
|
|
* @see Matrix#rotate
|
2011-06-03 12:45:01 -04:00
|
|
|
*
|
|
|
|
* @example {@paperscript}
|
|
|
|
* // Rotating an item:
|
|
|
|
*
|
|
|
|
* // Create a rectangle shaped path with its top left
|
|
|
|
* // point at {x: 80, y: 25} and a size of {width: 50, height: 50}:
|
|
|
|
* var path = new Path.Rectangle(new Point(80, 25), new Size(50, 50));
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // Rotate the path by 30 degrees:
|
|
|
|
* path.rotate(30);
|
|
|
|
*
|
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Rotating an item around a specific point:
|
|
|
|
*
|
|
|
|
* // Create a rectangle shaped path with its top left
|
2011-06-04 09:32:28 -04:00
|
|
|
* // point at {x: 175, y: 50} and a size of {width: 100, height: 100}:
|
|
|
|
* var topLeft = new Point(175, 50);
|
|
|
|
* var size = new Size(100, 100);
|
2011-06-03 12:45:01 -04:00
|
|
|
* var path = new Path.Rectangle(topLeft, size);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
2011-06-04 09:32:28 -04:00
|
|
|
* // Draw a circle shaped path in the center of the view,
|
|
|
|
* // to show the rotation point:
|
|
|
|
* var circle = new Path.Circle(view.center, 5);
|
|
|
|
* circle.fillColor = 'white';
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Each frame rotate the path 3 degrees around the center point
|
|
|
|
* // of the view:
|
|
|
|
* function onFrame(event) {
|
|
|
|
* path.rotate(3, view.center);
|
|
|
|
* }
|
2011-02-13 20:05:16 -05:00
|
|
|
*/
|
|
|
|
rotate: function(angle, center) {
|
2011-03-06 06:42:08 -05:00
|
|
|
return this.transform(new Matrix().rotate(angle,
|
2011-03-06 16:12:11 -05:00
|
|
|
center || this.getPosition()));
|
2011-02-13 20:05:16 -05:00
|
|
|
},
|
|
|
|
|
2011-06-17 13:09:15 -04:00
|
|
|
// TODO: Add test for item shearing, as it might be behaving oddly.
|
2011-02-13 20:05:16 -05:00
|
|
|
/**
|
2011-06-03 12:45:01 -04:00
|
|
|
* Shears the item by the given value from its center point, or optionally
|
|
|
|
* by a supplied point.
|
|
|
|
*
|
|
|
|
* @name Item#shear
|
|
|
|
* @function
|
|
|
|
* @param {Point} point
|
2011-06-17 13:04:00 -04:00
|
|
|
* @param {Point} [center={@link Item#position}]
|
2011-06-03 12:45:01 -04:00
|
|
|
* @see Matrix#shear
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* Shears the item by the given values from its center point, or optionally
|
|
|
|
* by a supplied point.
|
2011-02-13 20:05:16 -05:00
|
|
|
*
|
2011-06-16 14:26:50 -04:00
|
|
|
* @name Item#shear
|
|
|
|
* @function
|
2011-05-27 15:21:49 -04:00
|
|
|
* @param {Number} shx
|
|
|
|
* @param {Number} shy
|
2011-06-17 13:04:00 -04:00
|
|
|
* @param {Point} [center={@link Item#position}]
|
2011-05-22 17:39:54 -04:00
|
|
|
* @see Matrix#shear
|
2011-02-13 20:05:16 -05:00
|
|
|
*/
|
|
|
|
shear: function(shx, shy, center) {
|
2011-06-01 15:21:41 -04:00
|
|
|
// PORT: Add support for center back to Scriptographer too!
|
2011-02-15 18:23:40 -05:00
|
|
|
// See Matrix#scale for explanation of this:
|
2011-04-28 08:23:17 -04:00
|
|
|
if (arguments.length < 2 || typeof sy === 'object') {
|
2011-02-15 18:23:40 -05:00
|
|
|
center = shy;
|
|
|
|
shy = shx;
|
|
|
|
}
|
2011-03-06 06:42:08 -05:00
|
|
|
return this.transform(new Matrix().shear(shx, shy,
|
2011-03-06 16:12:11 -05:00
|
|
|
center || this.getPosition()));
|
2011-02-16 16:09:51 -05:00
|
|
|
},
|
2011-03-03 17:45:17 -05:00
|
|
|
|
2011-02-16 16:09:51 -05:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* Transform the item.
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-05-26 14:09:25 -04:00
|
|
|
* @param {Matrix} matrix
|
2011-05-27 15:21:49 -04:00
|
|
|
* @param {Array} flags Array of any of the following: 'objects', 'children',
|
2011-05-26 14:09:25 -04:00
|
|
|
* 'fill-gradients', 'fill-patterns', 'stroke-patterns', 'lines'.
|
|
|
|
* Default: ['objects', 'children']
|
2011-02-16 16:09:51 -05:00
|
|
|
*/
|
2011-05-26 14:09:25 -04:00
|
|
|
transform: function(matrix, flags) {
|
|
|
|
// TODO: Handle flags, add TransformFlag class and convert to bit mask
|
2011-06-01 05:49:43 -04:00
|
|
|
// for quicker checking.
|
|
|
|
// TODO: Call transform on chidren only if 'children' flag is provided.
|
2011-06-19 13:07:53 -04:00
|
|
|
if (this._transform) {
|
|
|
|
// TODO: Detect matrices that contain only translations and scaling
|
|
|
|
// and transform the cached _bounds and _position without
|
|
|
|
// recalculating each time.
|
2011-05-26 14:09:25 -04:00
|
|
|
this._transform(matrix, flags);
|
2011-06-19 17:20:28 -04:00
|
|
|
this._changed(Change.GEOMETRY);
|
2011-06-19 13:07:53 -04:00
|
|
|
}
|
2011-05-26 14:09:25 -04:00
|
|
|
// Transform position as well. Do not modify _position directly,
|
|
|
|
// since it's a LinkedPoint and would cause recursion!
|
|
|
|
if (this._position)
|
|
|
|
matrix._transformPoint(this._position, this._position, true);
|
|
|
|
if (this._children) {
|
|
|
|
for (var i = 0, l = this._children.length; i < l; i++) {
|
|
|
|
var child = this._children[i];
|
|
|
|
child.transform(matrix, flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// PORT: Return 'this' in all chainable commands
|
|
|
|
return this;
|
2011-02-16 16:09:51 -05:00
|
|
|
},
|
2011-03-03 07:19:43 -05:00
|
|
|
|
2011-06-18 21:26:51 -04:00
|
|
|
/**
|
|
|
|
* Transform the item so that its {@link #bounds} fit within the specified
|
|
|
|
* rectangle, without changing its aspect ratio.
|
|
|
|
*
|
|
|
|
* @param {Rectangle} rectangle
|
|
|
|
* @param {Boolean} [fill=false]
|
|
|
|
*
|
|
|
|
* @example {@paperscript height=100}
|
|
|
|
* // Fitting an item to the bounding rectangle of another item's bounding
|
|
|
|
* // rectangle:
|
|
|
|
*
|
|
|
|
* // Create a rectangle shaped path with its top left corner
|
|
|
|
* // at {x: 80, y: 25} and a size of {width: 75, height: 50}:
|
|
|
|
* var size = new Size(75, 50);
|
|
|
|
* var path = new Path.Rectangle(new Point(80, 25), size);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // Create a circle shaped path with its center at {x: 80, y: 50}
|
|
|
|
* // and a radius of 30.
|
|
|
|
* var circlePath = new Path.Circle(new Point(80, 50), 30);
|
|
|
|
* circlePath.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Fit the circlePath to the bounding rectangle of
|
|
|
|
* // the rectangular path:
|
|
|
|
* circlePath.fitBounds(path.bounds);
|
|
|
|
*
|
|
|
|
* @example {@paperscript height=100}
|
|
|
|
* // Fitting an item to the bounding rectangle of another item's bounding
|
|
|
|
* // rectangle with the fill parameter set to true:
|
|
|
|
*
|
|
|
|
* // Create a rectangle shaped path with its top left corner
|
|
|
|
* // at {x: 80, y: 25} and a size of {width: 75, height: 50}:
|
|
|
|
* var size = new Size(75, 50);
|
|
|
|
* var path = new Path.Rectangle(new Point(80, 25), size);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // Create a circle shaped path with its center at {x: 80, y: 50}
|
|
|
|
* // and a radius of 30.
|
|
|
|
* var circlePath = new Path.Circle(new Point(80, 50), 30);
|
|
|
|
* circlePath.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Fit the circlePath to the bounding rectangle of
|
|
|
|
* // the rectangular path:
|
|
|
|
* circlePath.fitBounds(path.bounds, true);
|
|
|
|
*
|
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Fitting an item to the bounding rectangle of the view
|
|
|
|
* var path = new Path.Circle(new Point(80, 50), 30);
|
|
|
|
* path.fillColor = 'red';
|
|
|
|
*
|
|
|
|
* // Fit the path to the bounding rectangle of the view:
|
|
|
|
* path.fitBounds(view.bounds);
|
|
|
|
*/
|
|
|
|
fitBounds: function(rectangle, fill) {
|
|
|
|
rectangle = Rectangle.read(arguments);
|
|
|
|
var bounds = this.getBounds(),
|
|
|
|
itemRatio = bounds.height / bounds.width,
|
|
|
|
rectRatio = rectangle.height / rectangle.width,
|
|
|
|
scale = (fill ? itemRatio > rectRatio : itemRatio < rectRatio)
|
|
|
|
? rectangle.width / bounds.width
|
|
|
|
: rectangle.height / bounds.height,
|
|
|
|
delta = rectangle.getCenter().subtract(bounds.getCenter()),
|
|
|
|
newBounds = new Rectangle(new Point(),
|
|
|
|
new Size(bounds.width * scale, bounds.height * scale));
|
|
|
|
newBounds.setCenter(rectangle.getCenter());
|
|
|
|
this.setBounds(newBounds);
|
|
|
|
},
|
|
|
|
|
2011-05-26 14:09:25 -04:00
|
|
|
/*
|
|
|
|
_transform: function(matrix, flags) {
|
|
|
|
// The code that performs the actual transformation of content,
|
|
|
|
// if defined. Item itself does not define this.
|
|
|
|
},
|
|
|
|
*/
|
2011-03-03 07:19:43 -05:00
|
|
|
|
2011-02-24 11:42:32 -05:00
|
|
|
// TODO: toString
|
2011-03-03 07:19:43 -05:00
|
|
|
|
|
|
|
statics: {
|
2011-04-26 10:39:16 -04:00
|
|
|
drawSelectedBounds: function(bounds, ctx, matrix) {
|
2011-05-16 07:29:52 -04:00
|
|
|
var coords = matrix._transformCorners(bounds);
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx.beginPath();
|
2011-04-18 12:46:39 -04:00
|
|
|
for (var i = 0; i < 8; i++)
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx[i == 0 ? 'moveTo' : 'lineTo'](coords[i], coords[++i]);
|
|
|
|
ctx.closePath();
|
|
|
|
ctx.stroke();
|
2011-04-18 12:46:39 -04:00
|
|
|
for (var i = 0; i < 8; i++) {
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx.beginPath();
|
2011-05-04 12:44:08 -04:00
|
|
|
ctx.rect(coords[i] - 2, coords[++i] - 2, 4, 4);
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx.fill();
|
2011-04-18 12:46:39 -04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-06-01 05:49:43 -04:00
|
|
|
// TODO: Implement View into the drawing.
|
2011-03-03 07:19:43 -05:00
|
|
|
// TODO: Optimize temporary canvas drawing to ignore parts that are
|
|
|
|
// outside of the visible view.
|
2011-04-26 10:39:16 -04:00
|
|
|
draw: function(item, ctx, param) {
|
2011-06-17 13:53:34 -04:00
|
|
|
if (!item._visible || item._opacity == 0)
|
2011-03-03 07:19:43 -05:00
|
|
|
return;
|
|
|
|
|
2011-04-26 10:39:16 -04:00
|
|
|
var tempCanvas, parentCtx;
|
2011-03-03 07:19:43 -05:00
|
|
|
// If the item has a blendMode or is defining an opacity, draw it on
|
|
|
|
// a temporary canvas first and composite the canvas afterwards.
|
|
|
|
// Paths with an opacity < 1 that both define a fillColor
|
|
|
|
// and strokeColor also need to be drawn on a temporary canvas first,
|
|
|
|
// since otherwise their stroke is drawn half transparent over their
|
|
|
|
// fill.
|
2011-06-17 13:53:34 -04:00
|
|
|
if (item._blendMode !== 'normal'
|
|
|
|
|| item._opacity < 1
|
2011-05-15 14:58:29 -04:00
|
|
|
&& !(item._segments && (!item.getFillColor()
|
2011-03-04 21:40:38 -05:00
|
|
|
|| !item.getStrokeColor()))) {
|
2011-03-04 20:26:12 -05:00
|
|
|
var bounds = item.getStrokeBounds() || item.getBounds();
|
2011-03-03 07:19:43 -05:00
|
|
|
if (!bounds.width || !bounds.height)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Floor the offset and ceil the size, so we don't cut off any
|
|
|
|
// antialiased pixels when drawing onto the temporary canvas.
|
2011-04-28 06:50:53 -04:00
|
|
|
var itemOffset = bounds.getTopLeft().floor(),
|
2011-05-03 04:34:07 -04:00
|
|
|
size = bounds.getSize().ceil().add(new Size(1, 1));
|
2011-03-03 07:19:43 -05:00
|
|
|
tempCanvas = CanvasProvider.getCanvas(size);
|
|
|
|
|
|
|
|
// Save the parent context, so we can draw onto it later
|
2011-04-26 10:39:16 -04:00
|
|
|
parentCtx = ctx;
|
2011-03-03 07:19:43 -05:00
|
|
|
|
2011-04-26 10:39:16 -04:00
|
|
|
// Set ctx to the context of the temporary canvas,
|
|
|
|
// so we draw onto it, instead of the parentCtx
|
|
|
|
ctx = tempCanvas.getContext('2d');
|
|
|
|
ctx.save();
|
2011-03-03 07:19:43 -05:00
|
|
|
|
|
|
|
// Translate the context so the topLeft of the item is at (0, 0)
|
|
|
|
// on the temporary canvas.
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx.translate(-itemOffset.x, -itemOffset.y);
|
2011-03-03 07:19:43 -05:00
|
|
|
}
|
2011-04-17 12:46:35 -04:00
|
|
|
var savedOffset;
|
|
|
|
if (itemOffset) {
|
|
|
|
savedOffset = param.offset;
|
|
|
|
param.offset = itemOffset;
|
|
|
|
}
|
2011-04-26 10:39:16 -04:00
|
|
|
item.draw(ctx, param);
|
2011-04-17 12:46:35 -04:00
|
|
|
if (itemOffset)
|
|
|
|
param.offset = savedOffset;
|
2011-03-03 07:19:43 -05:00
|
|
|
|
|
|
|
// If we created a temporary canvas before, composite it onto the
|
|
|
|
// parent canvas:
|
|
|
|
if (tempCanvas) {
|
|
|
|
|
|
|
|
// Restore the temporary canvas to its state before the
|
|
|
|
// translation matrix was applied above.
|
2011-04-26 10:39:16 -04:00
|
|
|
ctx.restore();
|
2011-03-03 07:19:43 -05:00
|
|
|
|
|
|
|
// If the item has a blendMode, use BlendMode#process to
|
|
|
|
// composite its canvas on the parentCanvas.
|
2011-06-17 13:53:34 -04:00
|
|
|
if (item._blendMode !== 'normal') {
|
2011-03-03 07:19:43 -05:00
|
|
|
// The pixel offset of the temporary canvas to the parent
|
|
|
|
// canvas.
|
|
|
|
var pixelOffset = itemOffset.subtract(param.offset);
|
2011-06-17 13:53:34 -04:00
|
|
|
BlendMode.process(item._blendMode, ctx, parentCtx,
|
|
|
|
item._opacity, pixelOffset);
|
2011-03-03 07:19:43 -05:00
|
|
|
} else {
|
|
|
|
// Otherwise we just need to set the globalAlpha before drawing
|
|
|
|
// the temporary canvas on the parent canvas.
|
2011-04-26 10:39:16 -04:00
|
|
|
parentCtx.save();
|
2011-06-17 13:53:34 -04:00
|
|
|
parentCtx.globalAlpha = item._opacity;
|
2011-04-26 10:39:16 -04:00
|
|
|
parentCtx.drawImage(tempCanvas,
|
2011-03-03 07:19:43 -05:00
|
|
|
itemOffset.x, itemOffset.y);
|
2011-04-26 10:39:16 -04:00
|
|
|
parentCtx.restore();
|
2011-03-03 07:19:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the temporary canvas, so it can be reused
|
|
|
|
CanvasProvider.returnCanvas(tempCanvas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-03-04 20:50:56 -05:00
|
|
|
}, new function() {
|
2011-05-22 17:39:54 -04:00
|
|
|
/**
|
2011-05-26 14:09:25 -04:00
|
|
|
* {@grouptitle Remove On Event}
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
|
|
|
* Removes the item when the events specified in the passed object literal
|
|
|
|
* occur.
|
|
|
|
* The object literal can contain the following values:
|
|
|
|
* Remove the item when the next {@link Tool#onMouseMove} event is
|
|
|
|
* fired: {@code object.move = true}
|
|
|
|
*
|
|
|
|
* Remove the item when the next {@link Tool#onMouseDrag} event is
|
|
|
|
* fired: {@code object.drag = true}
|
|
|
|
*
|
|
|
|
* Remove the item when the next {@link Tool#onMouseDown} event is
|
|
|
|
* fired: {@code object.down = true}
|
|
|
|
*
|
|
|
|
* Remove the item when the next {@link Tool#onMouseUp} event is
|
|
|
|
* fired: {@code object.up = true}
|
|
|
|
*
|
|
|
|
* @name Item#removeOn
|
|
|
|
* @function
|
|
|
|
* @param {Object} object
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Click and drag below:
|
2011-06-02 07:32:00 -04:00
|
|
|
* function onMouseDrag(event) {
|
|
|
|
* // Create a circle shaped path at the mouse position,
|
|
|
|
* // with a radius of 10:
|
|
|
|
* var path = new Path.Circle(event.point, 10);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* // Remove the path on the next onMouseDrag or onMouseDown event:
|
2011-06-02 07:32:00 -04:00
|
|
|
* path.removeOn({
|
|
|
|
* drag: true,
|
2011-06-03 12:45:01 -04:00
|
|
|
* down: true
|
2011-06-02 07:32:00 -04:00
|
|
|
* });
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2011-05-23 07:47:21 -04:00
|
|
|
* Removes the item when the next {@link Tool#onMouseMove} event is fired.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-23 07:47:21 -04:00
|
|
|
* @name Item#removeOnMove
|
2011-05-22 17:39:54 -04:00
|
|
|
* @function
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Move your mouse below:
|
2011-06-02 07:32:00 -04:00
|
|
|
* function onMouseMove(event) {
|
|
|
|
* // Create a circle shaped path at the mouse position,
|
|
|
|
* // with a radius of 10:
|
|
|
|
* var path = new Path.Circle(event.point, 10);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // On the next move event, automatically remove the path:
|
|
|
|
* path.removeOnMove();
|
|
|
|
* }
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2011-05-23 07:47:21 -04:00
|
|
|
* Removes the item when the next {@link Tool#onMouseDown} event is fired.
|
2011-06-14 17:59:45 -04:00
|
|
|
*
|
2011-05-23 07:47:21 -04:00
|
|
|
* @name Item#removeOnDown
|
2011-05-22 17:39:54 -04:00
|
|
|
* @function
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Click a few times below:
|
2011-06-02 07:32:00 -04:00
|
|
|
* function onMouseDown(event) {
|
|
|
|
* // Create a circle shaped path at the mouse position,
|
|
|
|
* // with a radius of 10:
|
|
|
|
* var path = new Path.Circle(event.point, 10);
|
2011-06-03 12:45:01 -04:00
|
|
|
* path.fillColor = 'black';
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
|
|
|
* // Remove the path, next time the mouse is pressed:
|
|
|
|
* path.removeOnDown();
|
|
|
|
* }
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2011-05-23 07:47:21 -04:00
|
|
|
* Removes the item when the next {@link Tool#onMouseDrag} event is fired.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-23 07:47:21 -04:00
|
|
|
* @name Item#removeOnDrag
|
2011-05-22 17:39:54 -04:00
|
|
|
* @function
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Click and drag below:
|
2011-06-02 07:32:00 -04:00
|
|
|
* function onMouseDrag(event) {
|
|
|
|
* // Create a circle shaped path at the mouse position,
|
|
|
|
* // with a radius of 10:
|
|
|
|
* var path = new Path.Circle(event.point, 10);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // On the next drag event, automatically remove the path:
|
|
|
|
* path.removeOnDrag();
|
|
|
|
* }
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes the item when the next {@link Tool#onMouseUp} event is fired.
|
2011-05-23 11:10:12 -04:00
|
|
|
*
|
2011-05-22 17:39:54 -04:00
|
|
|
* @name Item#removeOnUp
|
|
|
|
* @function
|
2011-06-02 07:32:00 -04:00
|
|
|
*
|
2011-06-03 12:45:01 -04:00
|
|
|
* @example {@paperscript height=200}
|
|
|
|
* // Click a few times below:
|
2011-06-02 07:32:00 -04:00
|
|
|
* function onMouseDown(event) {
|
|
|
|
* // Create a circle shaped path at the mouse position,
|
|
|
|
* // with a radius of 10:
|
|
|
|
* var path = new Path.Circle(event.point, 10);
|
|
|
|
* path.fillColor = 'black';
|
|
|
|
*
|
|
|
|
* // Remove the path, when the mouse is released:
|
|
|
|
* path.removeOnUp();
|
|
|
|
* }
|
2011-05-22 17:39:54 -04:00
|
|
|
*/
|
|
|
|
|
2011-03-09 13:35:03 -05:00
|
|
|
var sets = {
|
2011-03-19 20:11:02 -04:00
|
|
|
down: {}, drag: {}, up: {}, move: {}
|
2011-03-09 13:35:03 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
function removeAll(set) {
|
2011-05-03 03:54:13 -04:00
|
|
|
for (var id in set) {
|
2011-03-09 13:35:03 -05:00
|
|
|
var item = set[id];
|
|
|
|
item.remove();
|
2011-04-28 06:50:53 -04:00
|
|
|
for (var type in sets) {
|
2011-03-09 13:35:03 -05:00
|
|
|
var other = sets[type];
|
2011-04-17 12:46:35 -04:00
|
|
|
if (other != set && other[item.getId()])
|
2011-03-09 13:35:03 -05:00
|
|
|
delete other[item.getId()];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function installHandler(name) {
|
|
|
|
var handler = 'onMouse' + Base.capitalize(name);
|
|
|
|
// Inject a onMouse handler that performs all the behind the scene magic
|
|
|
|
// and calls the script's handler at the end, if defined.
|
|
|
|
var func = paper.tool[handler];
|
|
|
|
if (!func || !func._installed) {
|
|
|
|
var hash = {};
|
|
|
|
hash[handler] = function(event) {
|
2011-05-08 10:35:10 -04:00
|
|
|
// Always clear the drag set on mouseup
|
2011-04-28 08:23:17 -04:00
|
|
|
if (name === 'up')
|
2011-03-09 13:35:03 -05:00
|
|
|
sets.drag = {};
|
|
|
|
removeAll(sets[name]);
|
|
|
|
sets[name] = {};
|
|
|
|
// Call the script's overridden handler, if defined
|
2011-04-17 12:46:35 -04:00
|
|
|
if (this.base)
|
2011-03-09 13:35:03 -05:00
|
|
|
this.base(event);
|
2011-04-18 12:46:39 -04:00
|
|
|
};
|
2011-03-09 13:35:03 -05:00
|
|
|
paper.tool.inject(hash);
|
|
|
|
// Only install this handler once, and mark it as installed,
|
|
|
|
// to prevent repeated installing.
|
|
|
|
paper.tool[handler]._installed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-06-02 07:32:00 -04:00
|
|
|
// TODO: implement Item#removeOnFrame
|
2011-03-19 20:11:02 -04:00
|
|
|
return Base.each(['down', 'drag', 'up', 'move'], function(name) {
|
2011-03-09 13:35:03 -05:00
|
|
|
this['removeOn' + Base.capitalize(name)] = function() {
|
|
|
|
var hash = {};
|
|
|
|
hash[name] = true;
|
|
|
|
return this.removeOn(hash);
|
|
|
|
};
|
|
|
|
}, {
|
|
|
|
removeOn: function(obj) {
|
|
|
|
for (var name in obj) {
|
|
|
|
if (obj[name]) {
|
|
|
|
sets[name][this.getId()] = this;
|
|
|
|
// Since the drag set gets cleared in up, we need to make
|
|
|
|
// sure it's installed too
|
2011-04-28 08:23:17 -04:00
|
|
|
if (name === 'drag')
|
2011-03-09 13:35:03 -05:00
|
|
|
installHandler('up');
|
|
|
|
installHandler(name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
});
|
2011-03-03 11:32:55 -05:00
|
|
|
});
|