Merge remote-tracking branch 'origin/master'

This commit is contained in:
Jürg Lehni 2011-04-22 16:32:32 +01:00
commit 092c893d38
16 changed files with 422 additions and 168 deletions

View file

@ -37,6 +37,8 @@ var Document = this.Document = Base.extend({
this.symbols = [];
this.views = [new DocumentView(this)];
this.activeView = this.views[0];
this._selectedItems = {};
this._selectedItemCount = 0;
},
getCurrentStyle: function() {
@ -56,41 +58,53 @@ var Document = this.Document = Base.extend({
return false;
},
getSelectionContext: function(param) {
var context = this._selectionContext;
if (!context) {
var canvas = CanvasProvider.getCanvas(this.size);
context = this._selectionContext = canvas.getContext('2d');
context.strokeWidth = 1;
getSelectedItems: function() {
// TODO: return groups if their children are all selected,
// and filter out their children from the list.
var items = [];
Base.each(this._selectedItems, function(item) {
items.push(item);
});
return items;
},
// TODO: implement setSelectedItems?
_selectItem: function(item, select) {
if (select) {
this._selectedItemCount++;
this._selectedItems[item.getId()] = item;
} else {
this._selectedItemCount--;
delete this._selectedItems[item.getId()];
}
context.strokeStyle = context.fillStyle = param.layerColor;
return context;
},
draw: function() {
if (this.canvas) {
var context = this.context;
// Initial tests conclude that clearing the canvas using clearRect
// is always faster than setting canvas.width = canvas.width
// http://jsperf.com/clearrect-vs-setting-width/7
this.context.clearRect(0, 0,
context.clearRect(0, 0,
this.size.width + 1, this.size.height + 1);
this.context.save();
context.save();
var param = { offset: new Point(0, 0) };
for (var i = 0, l = this.layers.length; i < l; i++) {
// TODO: use Layer#color:
param.layerColor = '#4f7aff';
Item.draw(this.layers[i], this.context, param);
}
this.context.restore();
// If, during drawing, one of the paths was selected, there will
// be a selectionContext which needs to be composited onto the
// canvas:
if (this._selectionContext) {
var canvas = this._selectionContext.canvas;
this.context.drawImage(canvas, 0, 0);
CanvasProvider.returnCanvas(canvas);
this._selectionContext = null;
for (var i = 0, l = this.layers.length; i < l; i++)
Item.draw(this.layers[i], context, param);
context.restore();
// Draw the selection of the selected items in the document:
if (this._selectedItemCount > 0) {
context.save();
context.strokeWidth = 1;
// Todo: use Layer#color
context.strokeStyle = context.fillStyle = '#4f7aff';
param = { selection: true };
Base.each(this._selectedItems, function(item) {
item.draw(context, param);
});
context.restore();
}
}
},

View file

@ -19,7 +19,7 @@ var Item = this.Item = Base.extend({
initialize: function() {
paper.document.activeLayer.appendTop(this);
this.setStyle(this.document.getCurrentStyle());
this.setStyle(this._document.getCurrentStyle());
},
/**
@ -60,20 +60,39 @@ var Item = this.Item = Base.extend({
child.setSelected(selected);
}
} else {
this._selected = selected;
if (selected != this._selected) {
// TODO: when an item is removed or moved to another
// document, it needs to be removed from _selectedItems
this._selected = selected;
this._document._selectItem(this, selected);
}
}
},
getSelected: function() {
if (this._children) {
for (var i = 0, l = this._children.length; i < l; i++) {
var child = this._children[i];
if (child.getSelected())
isSelected: function() {
if (this.children) {
for (var i = 0, l = this.children.length; i < l; i++) {
if (this.children[i].isSelected())
return true;
}
} else {
return !!this._selected;
}
return false;
},
getDocument: function() {
return this._document;
},
setDocument: function(document) {
if (document != this._document) {
this._document = document;
if (this.children) {
for (var i = 0, l = this.children.length; i < l; i++)
this.children[i].setDocument(document);
}
}
},
// TODO: isFullySelected / setFullySelected
@ -252,6 +271,8 @@ var Item = this.Item = Base.extend({
* Removes the item.
*/
remove: function() {
if(this.isSelected())
this.setSelected(false);
return this.removeFromParent();
},
@ -749,7 +770,7 @@ var Item = this.Item = Base.extend({
item.removeFromParent();
this.children.splice(top ? this.children.length : 0, 0, item);
item.parent = this;
item.document = this.document;
item.setDocument(this._document);
return true;
}
return false;
@ -763,7 +784,7 @@ var Item = this.Item = Base.extend({
item.parent.children.splice(item.getIndex()
+ (above ? 1 : -1), 0, this);
this.parent = item.parent;
this.document = item.document;
this.setDocument(item._document);
return true;
}
return false;

View file

@ -19,13 +19,13 @@ var Layer = this.Layer = Group.extend({
initialize: function() {
this.children = [];
this.document = paper.document;
this.document.layers.push(this);
this._document = paper.document;
this._document.layers.push(this);
this.activate();
},
getIndex: function() {
return this.parent ? this.base() : this.document.layers.indexOf(this);
return this.parent ? this.base() : this._document.layers.indexOf(this);
},
/**
@ -34,7 +34,7 @@ var Layer = this.Layer = Group.extend({
*/
removeFromParent: function() {
if (!this.parent) {
return !!this.document.layers.splice(this.getIndex(), 1).length;
return !!this._document.layers.splice(this.getIndex(), 1).length;
} else {
return this.base();
}
@ -42,16 +42,16 @@ var Layer = this.Layer = Group.extend({
getNextSibling: function() {
return this.parent ? this.base()
: this.document.layers[this.getIndex() + 1] || null;
: this._document.layers[this.getIndex() + 1] || null;
},
getPreviousSibling: function() {
return this.parent ? this.base()
: this.document.layers[this.getIndex() - 1] || null;
: this._document.layers[this.getIndex() - 1] || null;
},
activate: function() {
this.document.activeLayer = this;
this._document.activeLayer = this;
}
}, new function () {
function move(above) {
@ -59,9 +59,9 @@ var Layer = this.Layer = Group.extend({
// if the item is a layer and contained within Document#layers
if (item instanceof Layer && !item.parent) {
this.removeFromParent();
item.document.layers.splice(item.getIndex() + (above ? 1 : -1),
item._document.layers.splice(item.getIndex() + (above ? 1 : -1),
0, this);
this.document = item.document;
this.setDocument(item._document);
return true;
} else {
return this.base(item);

View file

@ -53,13 +53,14 @@ var PlacedSymbol = this.PlacedSymbol = Item.extend({
},
draw: function(ctx, param) {
ctx.save();
this.matrix.applyToContext(ctx);
Item.draw(this.symbol.getDefinition(), ctx, param);
ctx.restore();
if (this.getSelected()) {
if (param.selection) {
Item.drawSelectedBounds(this.symbol._definition.getStrokeBounds(),
this.document.getSelectionContext(param), this.matrix);
ctx, this.matrix);
} else {
ctx.save();
this.matrix.applyToContext(ctx);
Item.draw(this.symbol.getDefinition(), ctx, param);
ctx.restore();
}
}

View file

@ -202,15 +202,15 @@ var Raster = this.Raster = Item.extend({
},
draw: function(ctx, param) {
ctx.save();
this.matrix.applyToContext(ctx);
ctx.drawImage(this._canvas || this._image,
-this._size.width / 2, -this._size.height / 2);
ctx.restore();
if (this.getSelected()) {
if (param.selection) {
var bounds = new Rectangle(this._size).setCenter(0, 0);
Item.drawSelectedBounds(bounds,
this.document.getSelectionContext(param), this.matrix);
Item.drawSelectedBounds(bounds, ctx, this.matrix);
} else {
ctx.save();
this.matrix.applyToContext(ctx);
ctx.drawImage(this._canvas || this._image,
-this._size.width / 2, -this._size.height / 2);
ctx.restore();
}
}
}, new function() {

View file

@ -44,6 +44,7 @@ var sources = [
'src/path/Segment.js',
'src/path/SegmentPoint.js',
'src/path/SelectionState.js',
'src/path/Curve.js',
'src/path/CurveLocation.js',
'src/path/PathItem.js',

View file

@ -92,6 +92,7 @@ Base.inject({
//#include "path/Segment.js"
//#include "path/SegmentPoint.js"
//#include "path/SelectionState.js"
//#include "path/Curve.js"
//#include "path/CurveLocation.js"
//#include "path/PathItem.js"

View file

@ -19,9 +19,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
this.base();
this.children = [];
if (items) {
for (var i = 0, l = items.length; i < l; i++) {
for (var i = 0, l = items.length; i < l; i++)
this.appendTop(items[i]);
}
}
},
@ -29,11 +28,11 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
// code (from a utility script?)
getBounds: function() {
if (this.children.length) {
var rect = this.children[0].getBounds();
var x1 = rect.x;
var y1 = rect.y;
var x2 = rect.x + rect.width;
var y2 = rect.y + rect.height;
var rect = this.children[0].getBounds(),
x1 = rect.x,
y1 = rect.y,
x2 = rect.x + rect.width,
y2 = rect.y + rect.height;
for (var i = 1, l = this.children.length; i < l; i++) {
var rect2 = this.children[i].getBounds();
x1 = Math.min(rect2.x, x1);
@ -64,9 +63,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
},
smooth: function() {
for (var i = 0, l = this.children.length; i < l; i++) {
for (var i = 0, l = this.children.length; i < l; i++)
this.children[i].smooth();
}
},
moveTo: function() {
@ -79,9 +77,8 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
var firstChild = this.children[0];
ctx.beginPath();
param.compound = true;
for (var i = 0, l = this.children.length; i < l; i++) {
for (var i = 0, l = this.children.length; i < l; i++)
Item.draw(this.children[i], ctx, param);
}
firstChild.setContextStyles(ctx);
var fillColor = firstChild.getFillColor(),
strokeColor = firstChild.getStrokeColor();
@ -106,15 +103,14 @@ var CompoundPath = this.CompoundPath = PathItem.extend({
var fields = {
moveBy: function() {
var point = arguments.length ? Point.read(arguments) : new Point();
var path = getCurrentPath(this);
var current = path.segments[path.segments.length - 1].point;
var point = arguments.length ? Point.read(arguments) : new Point(),
path = getCurrentPath(this),
current = path.segments[path.segments.length - 1]._point;
this.moveTo(current.add(point));
},
closePath: function() {
var path = getCurrentPath(this);
path.closed = true;
getCurrentPath(this).closed = true;
}
};

View file

@ -140,6 +140,17 @@ var Curve = this.Curve = Base.extend({
return curves && (curves[this._index1 - 1]
|| this._path.closed && curves[curves.length - 1]) || null;
},
// TODO: port back to Scriptographer?
setSelected: function(selected) {
this.getHandle1().setSelected(selected);
this.getHandle2().setSelected(selected);
},
// TODO: port back to Scriptographer?
isSelected: function() {
return this.getHandle1().isSelected() && this.getHandle2.isSelected();
},
getCurveValues: function() {
var p1 = this._segment1._point,

View file

@ -20,6 +20,7 @@ var Path = this.Path = PathItem.extend({
initialize: function(segments) {
this.base();
this.closed = false;
this._selectedSegmentCount = 0;
// Support both passing of segments as array or arguments
// If it is an array, it can also be a description of a point, so
// check its first entry for object as well
@ -128,6 +129,29 @@ var Path = this.Path = PathItem.extend({
}
},
isSelected: function() {
return this._selectedSegmentCount > 0;
},
setSelected: function(selected) {
var wasSelected = this.isSelected(),
length = this._segments.length;
if (!wasSelected != !selected && length)
this._document._selectItem(this, selected);
this._selectedSegmentCount = selected ? length : 0;
for (var i = 0; i < length; i++)
this._segments[i]._selectionState = selected
? SelectionState.POINT : null;
},
isFullySelected: function() {
return this._selectedSegmentCount == this._segments.length;
},
setFullySelected: function(selected) {
this.setSelected(selected);
},
// TODO: pointsToCurves([tolerance[, threshold[, cornerRadius[, scale]]]])
// TODO: curvesToPoints([maxPointDistance[, flatness]])
// TODO: reduceSegments([flatness])
@ -142,22 +166,22 @@ var Path = this.Path = PathItem.extend({
join: function(path) {
if (path != null) {
var segments = path.segments;
var last1 = this.getLastSegment();
var last2 = path.getLastSegment();
if (last1.getPoint().equals(last2.getPoint()))
var segments = path.segments,
last1 = this.getLastSegment(),
last2 = path.getLastSegment();
if (last1._point.equals(last2._point))
path.reverse();
var first2 = path.getFirstSegment();
if (last1.getPoint().equals(first2.getPoint())) {
last1.setHandleOut(first2.getHandleOut());
if (last1._point.equals(first2._point)) {
last1.setHandleOut(first2._handleOut);
for (var i = 1, l = segments.length; i < l; i++)
this._add(segments[i]);
} else {
var first1 = this.getFirstSegment();
if (first1.getPoint().equals(first2.getPoint()))
if (first1._point.equals(first2._point))
path.reverse();
if (first1.getPoint().equals(last2.getPoint())) {
first1.setHandleIn(last2.getHandleIn());
if (first1._point.equals(last2._point)) {
first1.setHandleIn(last2._handleIn);
// Prepend all segments from path except last one
for (var i = 0, l = segments.length - 1; i < l; i++)
this._add(segments[i], 0);
@ -170,8 +194,8 @@ var Path = this.Path = PathItem.extend({
// Close if they touch in both places
var first1 = this.getFirstSegment();
last1 = this.getLastSegment();
if (last1.getPoint().equals(first1.getPoint())) {
first1.setHandleIn(last1.getHandleIn());
if (last1._point.equals(first1._point)) {
first1.setHandleIn(last1._handleIn);
last1.remove();
this.closed = true;
}
@ -182,13 +206,13 @@ var Path = this.Path = PathItem.extend({
// todo: getLocation(point, precision)
getLocation: function(length) {
var curves = this.getCurves();
var currentLength = 0;
var curves = this.getCurves(),
currentLength = 0;
for (var i = 0, l = curves.length; i < l; i++) {
var startLength = currentLength;
var curve = curves[i];
var startLength = currentLength,
curve = curves[i];
currentLength += curve.getLength();
if(currentLength >= length) {
if (currentLength >= length) {
// found the segment within which the length lies
var t = curve.getParameter(length - startLength);
return new CurveLocation(curve, t);
@ -206,12 +230,12 @@ var Path = this.Path = PathItem.extend({
getLength: function(/* location */) {
var location;
if(arguments.length)
if (arguments.length)
location = arguments[0];
var curves = this.getCurves();
var index = location
? location.getIndex()
: curves.length;
var curves = this.getCurves(),
index = location
? location.getIndex()
: curves.length;
if (index != -1) {
var length = 0;
for (var i = 0; i < index; i++)
@ -254,26 +278,28 @@ var Path = this.Path = PathItem.extend({
function drawHandles(ctx, segments) {
for (var i = 0, l = segments.length; i < l; i++) {
var segment = segments[i],
handleIn = segment.handleIn,
handleOut = segment.handleOut,
point = segment.point,
rounded = point.round();
point = segment._point,
pointSelected = segment._selectionState == SelectionState.POINT;
// TODO: draw handles depending on selection state of
// segment.point and neighbouring segments.
drawHandle(ctx, point, handleIn);
drawHandle(ctx, point, handleOut);
if (pointSelected || segment.isSelected(segment._handleIn))
drawHandle(ctx, point, segment._handleIn);
if (pointSelected || segment.isSelected(segment._handleOut))
drawHandle(ctx, point, segment._handleOut);
// Draw a rectangle at segment.point:
ctx.save();
ctx.beginPath();
ctx.rect(rounded.x - 2, rounded.y - 2, 4, 4);
ctx.rect(point._x - 2, point._y - 2, 4, 4);
ctx.fill();
// TODO: Only draw white rectangle if point.isSelected()
// is false:
ctx.beginPath();
ctx.rect(rounded.x - 1, rounded.y - 1, 2, 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.restore();
if (!pointSelected) {
ctx.beginPath();
ctx.rect(point._x - 1, point._y - 1, 2, 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.restore();
}
}
}
@ -281,12 +307,11 @@ var Path = this.Path = PathItem.extend({
if (!handle.isZero()) {
handle = handle.add(point);
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.moveTo(point._x, point._y);
ctx.lineTo(handle.x, handle.y);
ctx.stroke();
ctx.beginPath();
var rounded = handle.round();
ctx.rect(rounded.x - 1, rounded.y - 1, 2, 2);
ctx.rect(handle.x - 1, handle.y - 1, 2, 2);
ctx.stroke();
}
}
@ -301,8 +326,8 @@ var Path = this.Path = PathItem.extend({
for (var i = 0; i < length; i++) {
var segment = segments[i],
point = segment._point,
x = point.x,
y = point.y,
x = point._x,
y = point._y,
handleIn = segment._handleIn;
if (i == 0) {
ctx.moveTo(x, y);
@ -312,34 +337,34 @@ var Path = this.Path = PathItem.extend({
} else {
ctx.bezierCurveTo(
outX, outY,
handleIn.x + x, handleIn.y + y,
handleIn._x + x, handleIn._y + y,
x, y
);
}
}
handleOut = segment._handleOut;
outX = handleOut.x + x;
outY = handleOut.y + y;
outX = handleOut._x + x;
outY = handleOut._y + y;
}
if (this.closed && length > 1) {
var segment = segments[0],
point = segment._point,
x = point.x,
y = point.y,
x = point._x,
y = point._y,
handleIn = segment._handleIn;
ctx.bezierCurveTo(outX, outY, handleIn.x + x, handleIn.y + y, x, y);
ctx.bezierCurveTo(outX, outY, handleIn._x + x, handleIn._y + y, x, y);
ctx.closePath();
}
// If the path is part of a compound path or doesn't have a fill or
// stroke, there is no need to continue.
var fillColor = this.getFillColor(),
strokeColor = this.getStrokeColor();
// If we are drawing onto the selection canvas, stroke the
// path and draw its handles.
// If we are drawing the selection of a path, stroke it and draw
// its handles:
if (param.selection) {
ctx.stroke();
drawHandles(ctx, this.segments);
drawHandles(ctx, this._segments);
} else {
// If the path is part of a compound path or doesn't have a fill or
// stroke, there is no need to continue.
var fillColor = this.getFillColor(),
strokeColor = this.getStrokeColor();
if (!param.compound && (fillColor || strokeColor)) {
this.setContextStyles(ctx);
ctx.save();
@ -358,14 +383,6 @@ var Path = this.Path = PathItem.extend({
}
ctx.restore();
}
// If the path is selected, draw it again on the separate
// selection canvas, which will be composited onto the canvas
// after drawing of the document is complete.
if (this.getSelected()) {
param.selection = true;
this.draw(this.document.getSelectionContext(param), param);
param.selection = false;
}
}
}
};
@ -400,6 +417,7 @@ var Path = this.Path = PathItem.extend({
this[name] = value;
}, []);
} else {
this.setSelected(false);
this._segments.length = 0;
}
for(var i = 0; i < length; i++) {
@ -417,10 +435,10 @@ var Path = this.Path = PathItem.extend({
* @return Solution vector.
*/
function getFirstControlPoints(rhs) {
var n = rhs.length;
var x = []; // Solution vector.
var tmp = []; // Temporary workspace.
var b = 2;
var n = rhs.length,
x = [], // Solution vector.
tmp = [], // Temporary workspace.
b = 2;
x[0] = rhs[0] / b;
// Decomposition and forward substitution.
for (var i = 1; i < n; i++) {
@ -446,8 +464,6 @@ var Path = this.Path = PathItem.extend({
beans: true,
smooth: function() {
var segments = this._segments;
// This code is based on the work by Oleg V. Polikarpotchkin,
// http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
// It was extended to support closed paths by averaging overlapping
@ -455,13 +471,15 @@ var Path = this.Path = PathItem.extend({
// Polikarpotchkin's closed curve solution, but reuses the same
// algorithm as for open paths, and is probably executing faster as
// well, so it is preferred.
var size = segments.length;
var segments = this._segments,
size = segments.length,
n = size,
// Add overlapping ends for averaging handles in closed paths
overlap;
if (size <= 2)
return;
var n = size;
// Add overlapping ends for averaging handles in closed paths
var overlap;
if (this.closed) {
// Overlap up to 4 points since averaging beziers affect the 4
// neighboring points
@ -489,17 +507,17 @@ var Path = this.Path = PathItem.extend({
// Set right hand side X values
for (var i = 1; i < n - 1; i++)
rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x;
rhs[0] = knots[0].x + 2 * knots[1].x;
rhs[n - 1] = 3 * knots[n - 1].x;
rhs[i] = 4 * knots[i]._x + 2 * knots[i + 1]._x;
rhs[0] = knots[0]._x + 2 * knots[1]._x;
rhs[n - 1] = 3 * knots[n - 1]._x;
// Get first control points X-values
var x = getFirstControlPoints(rhs);
// Set right hand side Y values
for (var i = 1; i < n - 1; i++)
rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y;
rhs[0] = knots[0].y + 2 * knots[1].y;
rhs[n - 1] = 3 * knots[n - 1].y;
rhs[i] = 4 * knots[i]._y + 2 * knots[i + 1]._y;
rhs[0] = knots[0]._y + 2 * knots[1]._y;
rhs[n - 1] = 3 * knots[n - 1]._y;
// Get first control points Y-values
var y = getFirstControlPoints(rhs);
@ -530,12 +548,12 @@ var Path = this.Path = PathItem.extend({
new Point(x[i], y[i]).subtract(segment._point));
if (i < n - 1)
handleIn = new Point(
2 * knots[i + 1].x - x[i + 1],
2 * knots[i + 1].y - y[i + 1]);
2 * knots[i + 1]._x - x[i + 1],
2 * knots[i + 1]._y - y[i + 1]);
else
handleIn = new Point(
(knots[n].x + x[n - 1]) / 2,
(knots[n].y + y[n - 1]) / 2);
(knots[n]._x + x[n - 1]) / 2,
(knots[n]._y + y[n - 1]) / 2);
}
}
if (this.closed && handleIn) {
@ -591,8 +609,8 @@ var Path = this.Path = PathItem.extend({
var current = getCurrentSegment(this);
// Convert to relative values:
current.setHandleOut(new Point(
handle1.x - current._point.x,
handle1.y - current._point.y));
handle1.x - current._point._x,
handle1.y - current._point._y));
// And add the new segment, with handleIn set to c2
this._add(new Segment(to, handle2.subtract(to), new Point()));
},
@ -626,8 +644,8 @@ var Path = this.Path = PathItem.extend({
var current = getCurrentSegment(this)._point;
// handle = (through - (1 - t)^2 * current - t^2 * to) /
// (2 * (1 - t) * t)
var t1 = 1 - t;
var handle = through.subtract(current.multiply(t1 * t1)).subtract(
var t1 = 1 - t,
handle = through.subtract(current.multiply(t1 * t1)).subtract(
to.multiply(t * t)).divide(2 * t * t1);
if (handle.isNaN())
throw new Error(
@ -653,8 +671,8 @@ var Path = this.Path = PathItem.extend({
: middle.add(-step.y, step.x);
}
var x1 = current._point.x, x2 = through.x, x3 = to.x,
y1 = current._point.y, y2 = through.y, y3 = to.y,
var x1 = current._point._x, x2 = through.x, x3 = to.x,
y1 = current._point._y, y2 = through.y, y3 = to.y,
f = x3 * x3 - x3 * x2 - x1 * x3 + x1 * x2 + y3 * y3 - y3 * y2
- y1 * y3 + y1 * y2,

View file

@ -125,12 +125,111 @@ var Segment = this.Segment = Base.extend({
return this._path && this._path._segments[this.getIndex() - 1] || null;
},
// TODO:
// isSelected: function() {
//
// }
//
// setSelected: function(pt, selected)
isSelected: function(/* point */) {
var point = arguments.length ? arguments[0] : this._point;
var state = this._selectionState;
if (point == this._point) {
return state == SelectionState.POINT;
} else if (point == this._handleIn) {
return (state & SelectionState.HANDLE_IN)
== SelectionState.HANDLE_IN;
} else if (point == this._handleOut) {
return (state & SelectionState.HANDLE_OUT)
== SelectionState.HANDLE_OUT;
}
return false;
},
// Todo: port setSelected(selected) back to Scriptographer
setSelected: function(/* pt, selected */) {
var pt, selected;
if (arguments.length == 2) {
// setSelected(pt, selected)
pt = arguments[0];
selected = arguments[1];
} else {
// setSelected(selected)
pt = this._point;
selected = arguments[0];
}
if (!this._path)
return;
var wasSelected = !!this._selectionState;
var state = this._selectionState,
pointSelected = state == SelectionState.POINT,
handleInSelected = (state & SelectionState.HANDLE_IN)
== SelectionState.HANDLE_IN,
handleOutSelected = (state & SelectionState.HANDLE_OUT)
== SelectionState.HANDLE_OUT,
previous = this.getPrevious(),
next = this.getNext(),
closed = this._path.closed,
segments = this._path._segments,
length = segments.length;
if (length > 1 && closed) {
if (previous == null)
previous = segments[length - 1];
if (next == null)
next = segments[0];
}
if (pt == this._point) {
if (pointSelected != selected) {
if (selected) {
handleInSelected = handleOutSelected = false;
} else {
// When deselecting a point, the handles get selected
// instead depending on the selection state of their
// neighbors.
handleInSelected = previous != null
&& (previous._point.isSelected()
|| previous._handleOut.isSelected());
handleOutSelected = next != null
&& (next._point.isSelected()
|| next._handleOut.isSelected());
}
pointSelected = selected;
}
} else if (pt == this._handleIn) {
if (handleInSelected != selected) {
// When selecting handles, the point get deselected.
if (selected)
pointSelected = false;
handleInSelected = selected;
}
} else if (pt == this._handleOut) {
if (handleOutSelected != selected) {
// When selecting handles, the point get deselected.
if (selected)
pointSelected = false;
handleOutSelected = selected;
}
}
this._selectionState = pointSelected
? SelectionState.POINT
: handleInSelected
? handleOutSelected
? SelectionState.HANDLE_BOTH
: SelectionState.HANDLE_IN
: handleOutSelected
? SelectionState.HANDLE_OUT
: null;
// If the selection state of the segment has changed, we need to let
// it's path know and possibly add or remove it from
// document._selectedItems
if (wasSelected == !this._selectionState) {
var path = this._path,
selectedItems = path._document._selectedItems;
if (!this._selectionState) {
path._selectedSegmentCount--;
if (path._selectedSegmentCount == 0)
path._document._selectItem(path, false);
} else {
path._selectedSegmentCount++;
if (path._selectedSegmentCount == 1)
path._document._selectItem(path, true);
}
}
},
reverse: function() {
return new Segment(this._point, this._handleOut, this._handleIn);
@ -141,8 +240,12 @@ var Segment = this.Segment = Base.extend({
},
remove: function() {
if (this._path && this._path._segments)
return !!this._path._segments.splice(this.getIndex(), 1).length;
if (this._path) {
this._path._segments.splice(this.getIndex(), 1);
if (this.isSelected())
this._path._selectedSegmentCount--;
return true;
}
return false;
},

View file

@ -29,7 +29,15 @@ var SegmentPoint = Point.extend({
this._y = y;
// this._segment._markDirty(DirtyFlags.BOUNDS);
},
setSelected: function(selected) {
this._segment.setSelected(this, selected);
},
isSelected: function() {
return this._segment.isSelected(this);
},
statics: {
create: function(segment, arg1, arg2) {
var point;

View file

@ -0,0 +1,22 @@
/*
* 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.
* http://paperjs.org/
* http://scriptographer.org/
*
* Distributed under the MIT license. See LICENSE file for details.
*
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
* http://lehni.org/ & http://jonathanpuckey.com/
*
* All rights reserved.
*/
var SelectionState = {
POINT: 1,
HANDLE_IN: 2,
HANDLE_OUT: 4,
HANDLE_BOTH: 6
};

View file

@ -75,4 +75,17 @@ test('path.remove()', function() {
path.remove();
equals(doc.activeLayer.children.length, 0);
});
test('Is the path deselected after setting a new list of segments?', function() {
var doc = new Document();
var path = new Path([0, 0]);
path.selected = true;
equals(path.selected, true);
equals(doc.selectedItems.length, 1);
path.segments = [[0, 10]];
equals(path.selected, false);
equals(doc.selectedItems.length, 0);
});

View file

@ -48,4 +48,14 @@ test('segment.remove()', function() {
var path = new Path([10, 10], [5, 5], [10, 10]);
path.segments[1].remove();
equals(path.segments.length, 2);
});
test('segment.selected', function() {
var doc = new Document();
var path = new Path([10, 20], [50, 100]);
path.segments[0].point.selected = true;
equals(path.segments[0].point.selected, true);
path.segments[0].point.selected = false;
equals(path.segments[0].point.selected, false);
});

View file

@ -147,4 +147,39 @@ test('reverseChildren()', function() {
equals(doc.activeLayer.firstChild == path, false);
equals(doc.activeLayer.firstChild == thirdPath, true);
equals(doc.activeLayer.lastChild == path, true);
})
});
test('Check item#document when moving items across documents', function() {
var doc1 = new Document();
var path = new Path();
var group = new Group();
group.appendTop(new Path());
equals(path.document == doc1, true);
var doc2 = new Document();
doc2.activeLayer.appendTop(path);
equals(path.document == doc2, true);
doc2.activeLayer.appendTop(group);
equals(group.children[0].document == doc2, true);
});
test('group.selected', function() {
var doc = new Document();
var path = new Path([0, 0]);
var path2 = new Path([0, 0]);
var group = new Group([path, path2]);
path.selected = true;
equals(group.selected, true);
path.selected = false;
equals(group.selected, false);
group.selected = true;
equals(path.selected, true);
equals(path2.selected, true);
group.selected = false;
equals(path.selected, false);
equals(path2.selected, false);
});