From b2cd8cdec29b5840705ecaf66694bb109c876729 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 15:25:25 +0200 Subject: [PATCH 01/23] Path: use the internal SegmentPoint#_x and #_y properties where possible. --- src/path/Path.js | 46 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 25 deletions(-) diff --git a/src/path/Path.js b/src/path/Path.js index 952a40ee..710fd48b 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -254,23 +254,20 @@ 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; // TODO: draw handles depending on selection state of // segment.point and neighbouring segments. - drawHandle(ctx, point, handleIn); - drawHandle(ctx, point, handleOut); + drawHandle(ctx, point, segment._handleIn); + 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.rect(point._x - 1, point._y - 1, 2, 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.restore(); @@ -281,12 +278,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(); } } @@ -489,17 +485,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 +526,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 +587,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())); }, @@ -653,8 +649,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, From 517793c48aa6855c535ed12a045783bb9bb835d9 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 15:47:00 +0200 Subject: [PATCH 02/23] Implement Item#get/setDocument and Document#selectedItems. --- src/document/Document.js | 1 + src/item/Item.js | 45 +++++++++++++++++++++++++++++++++------- src/item/Layer.js | 18 ++++++++-------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index 74285f2b..42c405d8 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -37,6 +37,7 @@ var Document = this.Document = Base.extend({ this.symbols = []; this.views = [new DocumentView(this)]; this.activeView = this.views[0]; + this._selectedItems = []; }, getCurrentStyle: function() { diff --git a/src/item/Item.js b/src/item/Item.js index f1b069db..ea71eebd 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -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,47 @@ 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; + var selectedItems = this._document._selectedItems; + if (selected) { + selectedItems.push(this); + } else { + // TODO: is there a faster way? + var index = selectedItems.indexOf(this); + if (index != -1) + selectedItems.splice(index, 1); + } + } } }, getSelected: function() { - if (this._children) { - for (var i = 0, l = this._children.length; i < l; i++) { - var child = this._children[i]; - if (child.getSelected()) + if (this.children) { + for (var i = 0, l = this.children.length; i < l; i++) { + if (this.children[i].getSelected()) 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 +279,8 @@ var Item = this.Item = Base.extend({ * Removes the item. */ remove: function() { + if(this._selected) + this.setSelected(false); return this.removeFromParent(); }, @@ -749,7 +778,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 +792,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; diff --git a/src/item/Layer.js b/src/item/Layer.js index 1958d0bb..6a41c2a3 100644 --- a/src/item/Layer.js +++ b/src/item/Layer.js @@ -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); From 732caec7bf9ba854dc169b3a40015d60457eaece Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 15:48:21 +0200 Subject: [PATCH 03/23] Speed up drawing of selected items. --- src/document/Document.js | 36 ++++++++++++------------------------ src/item/PlacedSymbol.js | 13 +++++++------ src/item/Raster.js | 16 ++++++++-------- src/path/Path.js | 38 +++++++++++++++----------------------- 4 files changed, 42 insertions(+), 61 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index 42c405d8..f08c409f 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -57,17 +57,6 @@ 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; - } - context.strokeStyle = context.fillStyle = param.layerColor; - return context; - }, - draw: function() { if (this.canvas) { // Initial tests conclude that clearing the canvas using clearRect @@ -77,21 +66,20 @@ var Document = this.Document = Base.extend({ this.size.width + 1, this.size.height + 1); this.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'; + for (var i = 0, l = this.layers.length; i < l; i++) 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; + + // Draw the selection of the selected items in the document: + var selectedItems = this._selectedItems, + length = selectedItems.length; + if (length) { + this.context.strokeWidth = 1; + // Todo: use Layer#color + this.context.strokeStyle = this.context.fillStyle = '#4f7aff'; + param = { selection: true }; + for (var i = 0; i < length; i++) + selectedItems[i].draw(this.context, param); } } }, diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js index ba85be27..2dbbf029 100644 --- a/src/item/PlacedSymbol.js +++ b/src/item/PlacedSymbol.js @@ -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(); } } diff --git a/src/item/Raster.js b/src/item/Raster.js index c3c35da0..c7b44aa9 100644 --- a/src/item/Raster.js +++ b/src/item/Raster.js @@ -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() { diff --git a/src/path/Path.js b/src/path/Path.js index 710fd48b..e3f678d4 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -297,8 +297,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); @@ -308,34 +308,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(); @@ -354,14 +354,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; - } } } }; From 63c3480ef4916381ef5bde9b2611c8ccae93c266 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 15:57:19 +0200 Subject: [PATCH 04/23] Add item tests for moving items across documents and selecting groups. --- test/tests/item.js | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/test/tests/item.js b/test/tests/item.js index 351618eb..9d29ce03 100644 --- a/test/tests/item.js +++ b/test/tests/item.js @@ -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); -}) \ No newline at end of file +}); + +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(); + var path2 = new Path(); + 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); +}); \ No newline at end of file From 73b97dbfba884272e847151dea48f49f7cce2bdb Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 18:06:06 +0200 Subject: [PATCH 05/23] Implement segment point selection. --- src/item/Item.js | 2 +- src/path/Path.js | 55 +++++++++++++++--- src/path/Segment.js | 120 ++++++++++++++++++++++++++++++++++++--- src/path/SegmentPoint.js | 10 +++- 4 files changed, 168 insertions(+), 19 deletions(-) diff --git a/src/item/Item.js b/src/item/Item.js index ea71eebd..2a043a20 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -279,7 +279,7 @@ var Item = this.Item = Base.extend({ * Removes the item. */ remove: function() { - if(this._selected) + if(this.getSelected()) this.setSelected(false); return this.removeFromParent(); }, diff --git a/src/path/Path.js b/src/path/Path.js index e3f678d4..09b00920 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -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,37 @@ var Path = this.Path = PathItem.extend({ } }, + getSelected: function() { + return this._selectedSegmentCount > 0; + }, + + setSelected: function(selected) { + var wasSelected = this.getSelected(); + var length = this._segments.length; + if (wasSelected != selected && length) { + var selectedItems = this._document._selectedItems; + if (selected) { + selectedItems.push(this); + } else { + // TODO: is there a faster way? + var index = selectedItems.indexOf(this); + if (index != -1) + selectedItems.splice(index, 1); + } + } + this._selectedSegmentCount = selected ? length : 0; + for (var i = 0; i < length; i++) + this._segments[i]._selectionState = selected ? '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]) @@ -254,23 +286,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], - point = segment._point; + point = segment._point, + pointSelected = segment._selectionState == 'point'; // TODO: draw handles depending on selection state of // segment.point and neighbouring segments. - drawHandle(ctx, point, segment._handleIn); - drawHandle(ctx, point, segment._handleOut); + if (pointSelected || segment.getSelected(segment._handleIn)) + drawHandle(ctx, point, segment._handleIn); + if (pointSelected || segment.getSelected(segment._handleOut)) + drawHandle(ctx, point, segment._handleOut); // Draw a rectangle at segment.point: ctx.save(); ctx.beginPath(); ctx.rect(point._x - 2, point._y - 2, 4, 4); ctx.fill(); - // TODO: Only draw white rectangle if point.isSelected() + // TODO: Only draw white rectangle if point.getSelected() // is false: - ctx.beginPath(); - ctx.rect(point._x - 1, point._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(); + } } } diff --git a/src/path/Segment.js b/src/path/Segment.js index 35e27cec..d25044c9 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -125,12 +125,112 @@ var Segment = this.Segment = Base.extend({ return this._path && this._path._segments[this.getIndex() - 1] || null; }, - // TODO: - // isSelected: function() { - // - // } - // - // setSelected: function(pt, selected) + getSelected: function(/* point */) { + var point = arguments.length ? arguments[0] : this.point; + var state = this._selectionState; + if (point == this.point) { + return state == 'point'; + } else if (point == this.handleIn) { + return state == 'handle-in' || state == 'handle-both'; + } else if (point == this.handleOut) { + return state == 'handle-out' || state == 'handle-both'; + } + return false; + }, + + 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 == 'point', + handleInSelected = state == 'handle-in' + || state == 'handle-both', + handleOutSelected = state == 'handle-out' + || state == 'handle-both', + 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.getSelected() + || previous._handleOut.getSelected()); + handleOutSelected = next != null + && (next._point.getSelected() + || next._handleOut.getSelected()); + } + 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 + ? 'point' + : handleInSelected + ? handleOutSelected + ? 'handle-both' + : 'handle-in' + : handleOutSelected + ? '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) { + var index = selectedItems.indexOf(this); + selectedItems.slice(index, 1); + } + } else { + path._selectedSegmentCount++; + if (path._selectedSegmentCount == 1) { + selectedItems.push(path); + } + } + } + + }, reverse: function() { return new Segment(this._point, this._handleOut, this._handleIn); @@ -141,8 +241,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.getSelected()) + this._path._selectedSegmentCount--; + return true; + } return false; }, diff --git a/src/path/SegmentPoint.js b/src/path/SegmentPoint.js index c3659939..4b2ec0f4 100644 --- a/src/path/SegmentPoint.js +++ b/src/path/SegmentPoint.js @@ -29,7 +29,15 @@ var SegmentPoint = Point.extend({ this._y = y; // this._segment._markDirty(DirtyFlags.BOUNDS); }, - + + setSelected: function(selected) { + this._segment.setSelected(this, selected); + }, + + getSelected: function() { + return this._segment.getSelected(this); + }, + statics: { create: function(segment, arg1, arg2) { var point; From ff9976ab57ddbf6fa6485424cce6c5fd94ba119f Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 18:10:47 +0200 Subject: [PATCH 06/23] Implement Document#getSelectedItems. --- src/document/Document.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/document/Document.js b/src/document/Document.js index f08c409f..e6079dc1 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -57,6 +57,14 @@ var Document = this.Document = Base.extend({ return false; }, + getSelectedItems: function() { + // TODO: return groups if their children are all selected, + // and filter out their children from the list. + return this._selectedItems; + }, + + // TODO: implement setSelectedItems? + draw: function() { if (this.canvas) { // Initial tests conclude that clearing the canvas using clearRect From cf2faa14e309f2299de2824d35cbd73f4f6c9326 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 18:43:22 +0200 Subject: [PATCH 07/23] Path#join: use internal variables. --- src/path/Path.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/path/Path.js b/src/path/Path.js index 09b00920..02236edb 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -177,19 +177,19 @@ var Path = this.Path = PathItem.extend({ var segments = path.segments; var last1 = this.getLastSegment(); var last2 = path.getLastSegment(); - if (last1.getPoint().equals(last2.getPoint())) + 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); @@ -202,8 +202,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; } From 7867a46c35661d6f27870aa22d1c0ec56e44e72d Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 18:45:36 +0200 Subject: [PATCH 08/23] Path#setSelected: do XOR correctly. --- src/path/Path.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/path/Path.js b/src/path/Path.js index 02236edb..8f633661 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -136,7 +136,7 @@ var Path = this.Path = PathItem.extend({ setSelected: function(selected) { var wasSelected = this.getSelected(); var length = this._segments.length; - if (wasSelected != selected && length) { + if (!wasSelected != !selected && length) { var selectedItems = this._document._selectedItems; if (selected) { selectedItems.push(this); From d831d667581cbcdd20bda87d8fba411aa80c1b05 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 19:37:51 +0200 Subject: [PATCH 09/23] Implement SelectionState.js: bitwise flags for segment selection state. --- src/load.js | 1 + src/paper.js | 1 + src/path/Path.js | 5 +++-- src/path/Segment.js | 29 +++++++++++++++-------------- src/path/SelectionState.js | 22 ++++++++++++++++++++++ 5 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 src/path/SelectionState.js diff --git a/src/load.js b/src/load.js index 7b9f27c5..7593512e 100644 --- a/src/load.js +++ b/src/load.js @@ -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', diff --git a/src/paper.js b/src/paper.js index 09ac6147..c16ae7cf 100644 --- a/src/paper.js +++ b/src/paper.js @@ -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" diff --git a/src/path/Path.js b/src/path/Path.js index 8f633661..1239a619 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -149,7 +149,8 @@ var Path = this.Path = PathItem.extend({ } this._selectedSegmentCount = selected ? length : 0; for (var i = 0; i < length; i++) - this._segments[i]._selectionState = selected ? 'point' : null; + this._segments[i]._selectionState = selected + ? SelectionState.POINT : null; }, isFullySelected: function() { @@ -287,7 +288,7 @@ var Path = this.Path = PathItem.extend({ for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i], point = segment._point, - pointSelected = segment._selectionState == 'point'; + pointSelected = segment._selectionState == SelectionState.POINT; // TODO: draw handles depending on selection state of // segment.point and neighbouring segments. if (pointSelected || segment.getSelected(segment._handleIn)) diff --git a/src/path/Segment.js b/src/path/Segment.js index d25044c9..8964a8e1 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -129,11 +129,13 @@ var Segment = this.Segment = Base.extend({ var point = arguments.length ? arguments[0] : this.point; var state = this._selectionState; if (point == this.point) { - return state == 'point'; + return state == SelectionState.POINT; } else if (point == this.handleIn) { - return state == 'handle-in' || state == 'handle-both'; + return (state & SelectionState.HANDLE_IN) + == SelectionState.HANDLE_IN; } else if (point == this.handleOut) { - return state == 'handle-out' || state == 'handle-both'; + return (state & SelectionState.HANDLE_OUT) + == SelectionState.HANDLE_OUT; } return false; }, @@ -153,11 +155,11 @@ var Segment = this.Segment = Base.extend({ return; var wasSelected = !!this._selectionState; var state = this._selectionState, - pointSelected = state == 'point', - handleInSelected = state == 'handle-in' - || state == 'handle-both', - handleOutSelected = state == 'handle-out' - || state == 'handle-both', + 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, @@ -202,13 +204,13 @@ var Segment = this.Segment = Base.extend({ } } this._selectionState = pointSelected - ? 'point' + ? SelectionState.POINT : handleInSelected ? handleOutSelected - ? 'handle-both' - : 'handle-in' + ? SelectionState.HANDLE_BOTH + : SelectionState.HANDLE_IN : handleOutSelected - ? 'handle-out' + ? 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 @@ -228,8 +230,7 @@ var Segment = this.Segment = Base.extend({ selectedItems.push(path); } } - } - + } }, reverse: function() { diff --git a/src/path/SelectionState.js b/src/path/SelectionState.js new file mode 100644 index 00000000..f5c76857 --- /dev/null +++ b/src/path/SelectionState.js @@ -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 +}; From cf541901dd2fb9e6b5451ccd71485a707b6007fd Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 19:51:49 +0200 Subject: [PATCH 10/23] Rename getSelected methods to isSelected. --- src/item/Item.js | 6 +++--- src/path/Path.js | 10 +++++----- src/path/Segment.js | 12 ++++++------ src/path/SegmentPoint.js | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/item/Item.js b/src/item/Item.js index 2a043a20..c1ae7530 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -77,10 +77,10 @@ var Item = this.Item = Base.extend({ } }, - getSelected: function() { + isSelected: function() { if (this.children) { for (var i = 0, l = this.children.length; i < l; i++) { - if (this.children[i].getSelected()) + if (this.children[i].isSelected()) return true; } } else { @@ -279,7 +279,7 @@ var Item = this.Item = Base.extend({ * Removes the item. */ remove: function() { - if(this.getSelected()) + if(this.isSelected()) this.setSelected(false); return this.removeFromParent(); }, diff --git a/src/path/Path.js b/src/path/Path.js index 1239a619..ba84e7fd 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -129,12 +129,12 @@ var Path = this.Path = PathItem.extend({ } }, - getSelected: function() { + isSelected: function() { return this._selectedSegmentCount > 0; }, setSelected: function(selected) { - var wasSelected = this.getSelected(); + var wasSelected = this.isSelected(); var length = this._segments.length; if (!wasSelected != !selected && length) { var selectedItems = this._document._selectedItems; @@ -291,16 +291,16 @@ var Path = this.Path = PathItem.extend({ pointSelected = segment._selectionState == SelectionState.POINT; // TODO: draw handles depending on selection state of // segment.point and neighbouring segments. - if (pointSelected || segment.getSelected(segment._handleIn)) + if (pointSelected || segment.isSelected(segment._handleIn)) drawHandle(ctx, point, segment._handleIn); - if (pointSelected || segment.getSelected(segment._handleOut)) + if (pointSelected || segment.isSelected(segment._handleOut)) drawHandle(ctx, point, segment._handleOut); // Draw a rectangle at segment.point: ctx.save(); ctx.beginPath(); ctx.rect(point._x - 2, point._y - 2, 4, 4); ctx.fill(); - // TODO: Only draw white rectangle if point.getSelected() + // TODO: Only draw white rectangle if point.isSelected() // is false: if (!pointSelected) { ctx.beginPath(); diff --git a/src/path/Segment.js b/src/path/Segment.js index 8964a8e1..c3934bc5 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -125,7 +125,7 @@ var Segment = this.Segment = Base.extend({ return this._path && this._path._segments[this.getIndex() - 1] || null; }, - getSelected: function(/* point */) { + isSelected: function(/* point */) { var point = arguments.length ? arguments[0] : this.point; var state = this._selectionState; if (point == this.point) { @@ -180,11 +180,11 @@ var Segment = this.Segment = Base.extend({ // instead depending on the selection state of their // neighbors. handleInSelected = previous != null - && (previous._point.getSelected() - || previous._handleOut.getSelected()); + && (previous._point.isSelected() + || previous._handleOut.isSelected()); handleOutSelected = next != null - && (next._point.getSelected() - || next._handleOut.getSelected()); + && (next._point.isSelected() + || next._handleOut.isSelected()); } pointSelected = selected; } @@ -244,7 +244,7 @@ var Segment = this.Segment = Base.extend({ remove: function() { if (this._path) { this._path._segments.splice(this.getIndex(), 1); - if (this.getSelected()) + if (this.isSelected()) this._path._selectedSegmentCount--; return true; } diff --git a/src/path/SegmentPoint.js b/src/path/SegmentPoint.js index 4b2ec0f4..43ca132e 100644 --- a/src/path/SegmentPoint.js +++ b/src/path/SegmentPoint.js @@ -34,8 +34,8 @@ var SegmentPoint = Point.extend({ this._segment.setSelected(this, selected); }, - getSelected: function() { - return this._segment.getSelected(this); + isSelected: function() { + return this._segment.isSelected(this); }, statics: { From abb0878a254b53207f750a35e2b46ade9fe05736 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 19:54:32 +0200 Subject: [PATCH 11/23] Curve: Implement selection of curves. --- src/path/Curve.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/path/Curve.js b/src/path/Curve.js index 1ee7d559..40d2a4b0 100644 --- a/src/path/Curve.js +++ b/src/path/Curve.js @@ -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, From 745f002cd3644b0660911a746c0a44901f78f5cb Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 20:00:11 +0200 Subject: [PATCH 12/23] Segment: add todo. --- src/path/Segment.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/path/Segment.js b/src/path/Segment.js index c3934bc5..e2fa2747 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -140,6 +140,7 @@ var Segment = this.Segment = Base.extend({ return false; }, + // Todo: port setSelected(selected) back to Scriptographer setSelected: function(/* pt, selected */) { var pt, selected; if (arguments.length == 2) { From be8ee90f7575af51deb74057626f47926d17d528 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 20:43:42 +0200 Subject: [PATCH 13/23] Document: use a hash for Document#_selectedItems to speed up adding to and removing from it. --- src/document/Document.js | 45 +++++++++++++++++++++++++++------------- src/item/Item.js | 10 +-------- src/path/Path.js | 13 ++---------- src/path/Segment.js | 11 ++++------ 4 files changed, 38 insertions(+), 41 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index e6079dc1..83ae4ed2 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -37,7 +37,8 @@ var Document = this.Document = Base.extend({ this.symbols = []; this.views = [new DocumentView(this)]; this.activeView = this.views[0]; - this._selectedItems = []; + this._selectedItems = {}; + this._selectedItemCount = 0; }, getCurrentStyle: function() { @@ -60,34 +61,50 @@ var Document = this.Document = Base.extend({ getSelectedItems: function() { // TODO: return groups if their children are all selected, // and filter out their children from the list. - return this._selectedItems; + var items = []; + Base.each(items, 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()]; + } + }, + 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++) - Item.draw(this.layers[i], this.context, param); - this.context.restore(); + Item.draw(this.layers[i], context, param); + context.restore(); // Draw the selection of the selected items in the document: - var selectedItems = this._selectedItems, - length = selectedItems.length; - if (length) { - this.context.strokeWidth = 1; + if (this._selectedItemCount > 0) { + context.save(); + context.strokeWidth = 1; // Todo: use Layer#color - this.context.strokeStyle = this.context.fillStyle = '#4f7aff'; + context.strokeStyle = context.fillStyle = '#4f7aff'; param = { selection: true }; - for (var i = 0; i < length; i++) - selectedItems[i].draw(this.context, param); + Base.each(this.selectedItems, function(item) { + item.draw(context, param); + }); + context.restore(); } } }, diff --git a/src/item/Item.js b/src/item/Item.js index c1ae7530..b2169462 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -64,15 +64,7 @@ var Item = this.Item = Base.extend({ // TODO: when an item is removed or moved to another // document, it needs to be removed from _selectedItems this._selected = selected; - var selectedItems = this._document._selectedItems; - if (selected) { - selectedItems.push(this); - } else { - // TODO: is there a faster way? - var index = selectedItems.indexOf(this); - if (index != -1) - selectedItems.splice(index, 1); - } + this._document._selectItem(this, selected); } } }, diff --git a/src/path/Path.js b/src/path/Path.js index ba84e7fd..766a87cb 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -136,17 +136,8 @@ var Path = this.Path = PathItem.extend({ setSelected: function(selected) { var wasSelected = this.isSelected(); var length = this._segments.length; - if (!wasSelected != !selected && length) { - var selectedItems = this._document._selectedItems; - if (selected) { - selectedItems.push(this); - } else { - // TODO: is there a faster way? - var index = selectedItems.indexOf(this); - if (index != -1) - selectedItems.splice(index, 1); - } - } + 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 diff --git a/src/path/Segment.js b/src/path/Segment.js index e2fa2747..bee56923 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -221,15 +221,12 @@ var Segment = this.Segment = Base.extend({ selectedItems = path._document._selectedItems; if (!this._selectionState) { path._selectedSegmentCount--; - if (path._selectedSegmentCount == 0) { - var index = selectedItems.indexOf(this); - selectedItems.slice(index, 1); - } + if (path._selectedSegmentCount == 0) + path._document._selectItem(path, true); } else { path._selectedSegmentCount++; - if (path._selectedSegmentCount == 1) { - selectedItems.push(path); - } + if (path._selectedSegmentCount == 1) + path._document._selectItem(path, false); } } }, From dfb5cbc5ebe001ac3996524b7486a2a53e713682 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 20:52:28 +0200 Subject: [PATCH 14/23] Fix two bugs in the selected items code. --- src/document/Document.js | 4 ++-- src/path/Segment.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index 83ae4ed2..2e65e99a 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -72,7 +72,7 @@ var Document = this.Document = Base.extend({ _selectItem: function(item, select) { if (select) { - this.selectedItemCount++; + this._selectedItemCount++; this._selectedItems[item.getId()] = item; } else { this._selectedItemCount--; @@ -101,7 +101,7 @@ var Document = this.Document = Base.extend({ // Todo: use Layer#color context.strokeStyle = context.fillStyle = '#4f7aff'; param = { selection: true }; - Base.each(this.selectedItems, function(item) { + Base.each(this._selectedItems, function(item) { item.draw(context, param); }); context.restore(); diff --git a/src/path/Segment.js b/src/path/Segment.js index bee56923..1048a335 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -222,11 +222,11 @@ var Segment = this.Segment = Base.extend({ if (!this._selectionState) { path._selectedSegmentCount--; if (path._selectedSegmentCount == 0) - path._document._selectItem(path, true); + path._document._selectItem(path, false); } else { path._selectedSegmentCount++; if (path._selectedSegmentCount == 1) - path._document._selectItem(path, false); + path._document._selectItem(path, true); } } }, From af0543dac816c4093900308a3cc74cc273ada265 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 21:01:31 +0200 Subject: [PATCH 15/23] Some cleanups in Path. --- src/path/Path.js | 56 ++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/path/Path.js b/src/path/Path.js index 766a87cb..4d04d2f7 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -134,8 +134,8 @@ var Path = this.Path = PathItem.extend({ }, setSelected: function(selected) { - var wasSelected = this.isSelected(); - var length = this._segments.length; + var wasSelected = this.isSelected(), + length = this._segments.length; if (!wasSelected != !selected && length) this._document._selectItem(this, selected); this._selectedSegmentCount = selected ? length : 0; @@ -166,14 +166,14 @@ var Path = this.Path = PathItem.extend({ join: function(path) { if (path != null) { - var segments = path.segments; - var last1 = this.getLastSegment(); - var last2 = path.getLastSegment(); + var segments = path.segments, + last1 = this.getLastSegment(), + last2 = path.getLastSegment(); if (last1._point.equals(last2._point)) path.reverse(); var first2 = path.getFirstSegment(); if (last1._point.equals(first2._point)) { - last1.setHandleOut(first2.handleOut); + last1.setHandleOut(first2._handleOut); for (var i = 1, l = segments.length; i < l; i++) this._add(segments[i]); } else { @@ -206,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); @@ -230,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++) @@ -434,10 +434,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++) { @@ -463,8 +463,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 @@ -472,13 +470,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 @@ -643,8 +643,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( From 1ea0a811b53fd9e70efe0d549c30ca36c205794a Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 21:06:35 +0200 Subject: [PATCH 16/23] Use internal variables in CompoundPath#moveBy and Segment#isSelected. --- src/path/CompoundPath.js | 2 +- src/path/Segment.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index 6454cbcc..c5953d78 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -108,7 +108,7 @@ var CompoundPath = this.CompoundPath = PathItem.extend({ 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 current = path.segments[path.segments.length - 1]._point; this.moveTo(current.add(point)); }, diff --git a/src/path/Segment.js b/src/path/Segment.js index 1048a335..440f6020 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -126,14 +126,14 @@ var Segment = this.Segment = Base.extend({ }, isSelected: function(/* point */) { - var point = arguments.length ? arguments[0] : this.point; + var point = arguments.length ? arguments[0] : this._point; var state = this._selectionState; - if (point == this.point) { + if (point == this._point) { return state == SelectionState.POINT; - } else if (point == this.handleIn) { + } else if (point == this._handleIn) { return (state & SelectionState.HANDLE_IN) == SelectionState.HANDLE_IN; - } else if (point == this.handleOut) { + } else if (point == this._handleOut) { return (state & SelectionState.HANDLE_OUT) == SelectionState.HANDLE_OUT; } From 11063af8dfd1a831b39e57ec14f16916f05cd88b Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Thu, 21 Apr 2011 21:12:48 +0200 Subject: [PATCH 17/23] CompoundPath cleanups. --- src/path/CompoundPath.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index c5953d78..872fec2d 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -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; } }; From 4d89d6c23529da3766b93f2aa04cec46d47ef86a Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 11:39:12 +0200 Subject: [PATCH 18/23] Path#setSegments: reset _selectedSegmentCount when setting a new segment list. --- src/path/Path.js | 2 +- test/tests/Path.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/path/Path.js b/src/path/Path.js index 4d04d2f7..fc19ff87 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -417,7 +417,7 @@ var Path = this.Path = PathItem.extend({ this[name] = value; }, []); } else { - this._segments.length = 0; + this._segments.length = this._selectedSegmentCount = 0; } for(var i = 0; i < length; i++) { this._add(Segment.read(segments, i, 1)); diff --git a/test/tests/Path.js b/test/tests/Path.js index 1a21dec0..99bf6a70 100644 --- a/test/tests/Path.js +++ b/test/tests/Path.js @@ -75,4 +75,14 @@ test('path.remove()', function() { path.remove(); equals(doc.activeLayer.children.length, 0); +}); + + +test('path.remove()', function() { + var doc = new Document(); + var path = new Path([0, 0]); + path.selected = true; + equals(path.selected, true); + path.segments = [[0, 10]]; + equals(path.selected, false); }); \ No newline at end of file From c9898ef3b0ad91eeae0ab40c6e6110caa6087748 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 11:40:54 +0200 Subject: [PATCH 19/23] Path tests: rename test. --- test/tests/Path.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tests/Path.js b/test/tests/Path.js index 99bf6a70..61584d25 100644 --- a/test/tests/Path.js +++ b/test/tests/Path.js @@ -78,7 +78,7 @@ test('path.remove()', function() { }); -test('path.remove()', function() { +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; From c1ec991aee6f96079d73c2a173358a99b0f44cef Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 11:41:32 +0200 Subject: [PATCH 20/23] Item test: initialize paths with segments, so they can be selected. --- test/tests/item.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/tests/item.js b/test/tests/item.js index 9d29ce03..f7191e4f 100644 --- a/test/tests/item.js +++ b/test/tests/item.js @@ -166,8 +166,8 @@ test('Check item#document when moving items across documents', function() { test('group.selected', function() { var doc = new Document(); - var path = new Path(); - var path2 = new Path(); + 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); From 2496f08b17a8ad4cda46221e0a2d99503b4f2704 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 11:42:07 +0200 Subject: [PATCH 21/23] Segment tests: add a test for segment selection. --- test/tests/Segment.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/tests/Segment.js b/test/tests/Segment.js index 2c87ef98..b4124911 100644 --- a/test/tests/Segment.js +++ b/test/tests/Segment.js @@ -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); }); \ No newline at end of file From 578269d0c1fd19b158a9f793704e9e3288452420 Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 11:52:24 +0200 Subject: [PATCH 22/23] Add failing path selection test. --- test/tests/Path.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/tests/Path.js b/test/tests/Path.js index 61584d25..6fe0e4ac 100644 --- a/test/tests/Path.js +++ b/test/tests/Path.js @@ -83,6 +83,9 @@ test('Is the path deselected after setting a new list of segments?', function() 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); }); \ No newline at end of file From 538f360a6bb8c65888e0c3c789c834a653065e8a Mon Sep 17 00:00:00 2001 From: Jonathan Puckey Date: Fri, 22 Apr 2011 12:30:38 +0200 Subject: [PATCH 23/23] Fix Document#getSelectedItems & Path#setSegments. --- src/document/Document.js | 2 +- src/path/Path.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index 2e65e99a..287a220b 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -62,7 +62,7 @@ var Document = this.Document = Base.extend({ // TODO: return groups if their children are all selected, // and filter out their children from the list. var items = []; - Base.each(items, function(item) { + Base.each(this._selectedItems, function(item) { items.push(item); }); return items; diff --git a/src/path/Path.js b/src/path/Path.js index fc19ff87..33d64adb 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -417,7 +417,8 @@ var Path = this.Path = PathItem.extend({ this[name] = value; }, []); } else { - this._segments.length = this._selectedSegmentCount = 0; + this.setSelected(false); + this._segments.length = 0; } for(var i = 0; i < length; i++) { this._add(Segment.read(segments, i, 1));