diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js index e46be96651..6d03fbe068 100644 --- a/src/basic/Rectangle.js +++ b/src/basic/Rectangle.js @@ -904,13 +904,18 @@ new function() { * @default false */ isSelected: function() { - return !!(this._owner._selection & /*#=*/ItemSelection.BOUNDS); + return this._owner._boundsSelected; }, setSelected: function(selected) { var owner = this._owner; - if (owner.changeSelection) { - owner.changeSelection(/*#=*/ItemSelection.BOUNDS, selected); + if (owner.setSelected) { + owner._boundsSelected = selected; + // Update the owner's selected state too, so the bounds + // actually get drawn. When deselecting, take a path's + // _segmentSelection into account too, since it will + // have to remain selected even when bounds are deselected + owner.setSelected(selected || owner._segmentSelection > 0); } } }) diff --git a/src/constants.js b/src/constants.js index b360edf04d..64f54c5a75 100644 --- a/src/constants.js +++ b/src/constants.js @@ -12,5 +12,4 @@ /*#*/ include('util/Numerical.js'); /*#*/ include('item/ChangeFlag.js'); -/*#*/ include('item/ItemSelection.js'); /*#*/ include('path/SegmentSelection.js'); diff --git a/src/item/Item.js b/src/item/Item.js index 6189525d77..145f83144e 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -51,30 +51,22 @@ var Item = Base.extend(Emitter, /** @lends Item# */{ _applyMatrix: true, _canApplyMatrix: true, _canScaleStroke: false, - _pivot: null, - _visible: true, - _blendMode: 'normal', - _opacity: 1, - _locked: false, - _guide: false, - _clipMask: false, - _selection: 0, _boundsSelected: false, _selectChildren: false, // Provide information about fields to be serialized, with their defaults - // that can be omitted. + // that can be ommited. _serializeFields: { name: null, applyMatrix: null, matrix: new Matrix(), pivot: null, + locked: false, visible: true, blendMode: 'normal', opacity: 1, - locked: false, guide: false, - clipMask: false, selected: false, + clipMask: false, data: {} } }, @@ -435,6 +427,7 @@ new function() { // Injection scope for various item event handlers * @default false * @ignore */ + _locked: false, /** * Specifies whether the item is visible. When set to `false`, the item @@ -455,6 +448,7 @@ new function() { // Injection scope for various item event handlers * // Hide the path: * path.visible = false; */ + _visible: true, /** * The blend mode with which the item is composited onto the canvas. Both @@ -496,6 +490,7 @@ new function() { // Injection scope for various item event handlers * // Set the blend mode of circle2: * circle2.blendMode = 'multiply'; */ + _blendMode: 'normal', /** * The opacity of the item as a value between `0` and `1`. @@ -523,6 +518,7 @@ new function() { // Injection scope for various item event handlers * // Make circle2 50% transparent: * circle2.opacity = 0.5; */ + _opacity: 1, // TODO: Implement guides /** @@ -534,26 +530,7 @@ new function() { // Injection scope for various item event handlers * @default true * @ignore */ - - getSelection: function() { - return this._selection; - }, - - setSelection: function(selection) { - if (selection !== this._selection) { - this._selection = selection; - var project = this._project; - if (project) { - project._updateSelection(this); - this._changed(/*#=*/Change.ATTRIBUTE); - } - } - }, - - changeSelection: function(flag, selected) { - var selection = this._selection; - this.setSelection(selected ? selection | flag : selection & ~flag); - }, + _guide: false, /** * Specifies whether the item is selected. This will also return `true` for @@ -588,29 +565,39 @@ new function() { // Injection scope for various item event handlers if (children[i].isSelected()) return true; } - return !!(this._selection & /*#=*/ItemSelection.ITEM); + return this._selected; }, - setSelected: function(selected) { - if (this._selectChildren) { + setSelected: function(selected, noChildren) { + // Don't recursively call #setSelected() if it was called with + // noChildren set to true, see #setFullySelected(). + if (!noChildren && this._selectChildren) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) children[i].setSelected(selected); } - this.changeSelection(/*#=*/ItemSelection.ITEM, selected); + if ((selected = !!selected) ^ this._selected) { + this._selected = selected; + var project = this._project; + if (project) { + project._updateSelection(this); + this._changed(/*#=*/Change.ATTRIBUTE); + } + } }, + _selected: false, + isFullySelected: function() { - var children = this._children, - selected = !!(this._selection & /*#=*/ItemSelection.ITEM); - if (children && selected) { + var children = this._children; + if (children && this._selected) { for (var i = 0, l = children.length; i < l; i++) if (!children[i].isFullySelected()) return false; return true; } // If there are no children, this is the same as #selected - return selected; + return this._selected; }, setFullySelected: function(selected) { @@ -619,7 +606,8 @@ new function() { // Injection scope for various item event handlers for (var i = 0, l = children.length; i < l; i++) children[i].setFullySelected(selected); } - this.changeSelection(/*#=*/ItemSelection.ITEM, selected); + // Pass true for hidden noChildren argument + this.setSelected(selected, true); }, /** @@ -650,6 +638,8 @@ new function() { // Injection scope for various item event handlers } }, + _clipMask: false, + // TODO: get/setIsolated (print specific feature) // TODO: get/setKnockout (print specific feature) // TODO: get/setAlphaIsShape @@ -790,7 +780,9 @@ new function() { // Injection scope for various item event handlers this._pivot = Point.read(arguments, 0, { clone: true, readNull: true }); // No need for _changed() since the only thing this affects is _position this._position = undefined; - } + }, + + _pivot: null, }, Base.each({ // Produce getters for bounds properties: getStrokeBounds: { stroke: true }, getHandleBounds: { handle: true }, @@ -1575,9 +1567,9 @@ new function() { // Injection scope for various item event handlers // in case #applyMatrix is true. this.setApplyMatrix(source._applyMatrix); this.setPivot(source._pivot); - // Copy over the selection state, use setSelection so the item - // is also added to Project#_selectionItems if it is selected. - this.setSelection(source._selection); + // Copy over the selection state, use setSelected so the item + // is also added to Project#selectedItems if it is selected. + this.setSelected(source._selected); // Copy over data and name as well. var data = source._data, name = source._name; @@ -1886,7 +1878,7 @@ new function() { // Injection scope for hit-test functions shared with project // See if we should check self (own content), by filtering for type, // guides and selected items if that's required. var checkSelf = !(options.guides && !this._guide - || options.selected && !this.isSelected(true) + || options.selected && !this._selected // Support legacy Item#type property to match hyphenated // class-names. || options.type && options.type !== Base.hyphenate(this._class) @@ -4268,27 +4260,23 @@ new function() { // Injection scope for hit-test functions shared with project return updated; }, - _drawSelection: function(ctx, matrix, size, selectionItems, updateVersion) { - var selection = this._selection, - itemSelected = selection & /*#=*/ItemSelection.ITEM, - boundsSelected = selection & /*#=*/ItemSelection.BOUNDS - || itemSelected && this._boundsSelected; - if (!this._drawSelected) - itemSelected = false; - if ((itemSelected || boundsSelected) && this._isUpdated(updateVersion)) { + _drawSelection: function(ctx, matrix, size, selectedItems, updateVersion) { + if ((this._drawSelected || this._boundsSelected) + && this._isUpdated(updateVersion)) { // Allow definition of selected color on a per item and per // layer level, with a fallback to #009dec var layer, - color = this.getSelectedColor(true) || (layer = this.getLayer()) - && layer.getSelectedColor(true), + color = this.getSelectedColor(true) + || (layer = this.getLayer()) && layer.getSelectedColor(true), mx = matrix.appended(this.getGlobalMatrix(true)); ctx.strokeStyle = ctx.fillStyle = color ? color.toCanvasStyle(ctx) : '#009dec'; - if (itemSelected) - this._drawSelected(ctx, mx, selectionItems); - if (boundsSelected) { + if (this._drawSelected) + this._drawSelected(ctx, mx, selectedItems); + if (this._boundsSelected) { var half = size / 2, - coords = mx._transformCorners(this.getInternalBounds()); + coords = mx._transformCorners( + this.getInternalBounds()); // Now draw a rectangle that connects the transformed // bounds corners, and draw the corners. ctx.beginPath(); diff --git a/src/item/ItemSelection.js b/src/item/ItemSelection.js deleted file mode 100644 index 7878e4f289..0000000000 --- a/src/item/ItemSelection.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Paper.js - The Swiss Army Knife of Vector Graphics Scripting. - * http://paperjs.org/ - * - * Copyright (c) 2011 - 2016, Juerg Lehni & Jonathan Puckey - * http://scratchdisk.com/ & http://jonathanpuckey.com/ - * - * Distributed under the MIT license. See LICENSE file for details. - * - * All rights reserved. - */ - -var ItemSelection = { - ITEM: 1, - BOUNDS: 2, - PIVOT: 4 -}; diff --git a/src/item/Project.js b/src/item/Project.js index c84791f68b..0cc642721b 100644 --- a/src/item/Project.js +++ b/src/item/Project.js @@ -62,8 +62,8 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ // (e.g. PointText#_getBounds) this._view = View.create(this, element || CanvasProvider.getCanvas(1, 1)); - this._selectionItems = {}; - this._selectionCount = 0; + this._selectedItems = {}; + this._selectedItemCount = 0; // See Item#draw() for an explanation of _updateVersion this._updateVersion = 0; // Change tracking, not in use for now. Activate once required: @@ -282,15 +282,15 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ // TODO: Return groups if their children are all selected, and filter // out their children from the list. // TODO: The order of these items should be that of their drawing order. - var selectionItems = this._selectionItems, + var selectedItems = this._selectedItems, items = []; - for (var id in selectionItems) { - var item = selectionItems[id], - selection = item._selection; - if (selection & /*#=*/ItemSelection.ITEM && item.isInserted()) { + for (var id in selectedItems) { + var item = selectedItems[id]; + if (item.isInserted()) { items.push(item); - } else if (!selection) { - this._updateSelection(item); + } else { + this._selectedItemCount--; + delete selectedItems[id]; } } return items; @@ -299,15 +299,15 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ _updateSelection: function(item) { var id = item._id, - selectionItems = this._selectionItems; - if (item._selection) { - if (selectionItems[id] !== item) { - this._selectionCount++; - selectionItems[id] = item; + selectedItems = this._selectedItems; + if (item._selected) { + if (selectedItems[id] !== item) { + this._selectedItemCount++; + selectedItems[id] = item; } - } else if (selectionItems[id] === item) { - this._selectionCount--; - delete selectionItems[id]; + } else if (selectedItems[id] === item) { + this._selectedItemCount--; + delete selectedItems[id]; } }, @@ -324,9 +324,9 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ * Deselects all selected items in the project. */ deselectAll: function() { - var selectionItems = this._selectionItems; - for (var i in selectionItems) - selectionItems[i].setFullySelected(false); + var selectedItems = this._selectedItems; + for (var i in selectedItems) + selectedItems[i].setFullySelected(false); }, /** @@ -868,10 +868,10 @@ var Project = PaperScopeItem.extend(/** @lends Project# */{ ctx.restore(); // Draw the selection of the selected items in the project: - if (this._selectionCount > 0) { + if (this._selectedItemCount > 0) { ctx.save(); ctx.strokeWidth = 1; - var items = this._selectionItems, + var items = this._selectedItems, size = this._scope.settings.handleSize, version = this._updateVersion; for (var id in items) { diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index 42a03e5cc9..9c0ccc1aad 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -293,14 +293,14 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{ } }, - _drawSelected: function(ctx, matrix, selectionItems) { + _drawSelected: function(ctx, matrix, selectedItems) { var children = this._children; for (var i = 0, l = children.length; i < l; i++) { var child = children[i], mx = child._matrix; // Do not draw this child now if it's separately marked as selected, // as it would be drawn twice otherwise. - if (!selectionItems[child._id]) { + if (!selectedItems[child._id]) { child._drawSelected(ctx, mx.isIdentity() ? matrix : matrix.appended(mx)); } diff --git a/src/path/Path.js b/src/path/Path.js index 6594d7f424..0dbb15ef02 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -935,8 +935,8 @@ var Path = PathItem.extend(/** @lends Path# */{ */ isFullySelected: function() { var length = this._segments.length; - return this.isSelected(true) && length > 0 && this._segmentSelection - === length * /*#=*/SegmentSelection.ALL; + return this._selected && length > 0 && this._segmentSelection + === length * /*#=*/SegmentSelection.SEGMENT; }, setFullySelected: function(selected) { @@ -947,20 +947,22 @@ var Path = PathItem.extend(/** @lends Path# */{ this.setSelected(selected); }, - setSelection: function setSelection(selection) { + setSelected: function setSelected(selected) { // Deselect all segments when path is marked as not selected - if (!(selection & /*#=*/ItemSelection.ITEM)) + if (!selected) this._selectSegments(false); - setSelection.base.call(this, selection); + // No need to pass true for noChildren since Path has none anyway. + setSelected.base.call(this, selected); }, _selectSegments: function(selected) { - var segments = this._segments, - length = segments.length, - selection = selected ? /*#=*/SegmentSelection.ALL : 0; - this._segmentSelection = selection * length; - for (var i = 0; i < length; i++) - segments[i]._selection = selection; + var length = this._segments.length; + this._segmentSelection = selected + ? length * /*#=*/SegmentSelection.SEGMENT : 0; + for (var i = 0; i < length; i++) { + this._segments[i]._selection = selected + ? /*#=*/SegmentSelection.SEGMENT : 0; + } }, _updateSelection: function(segment, oldSelection, newSelection) { diff --git a/src/path/Segment.js b/src/path/Segment.js index 1a2b7aa4cf..7a4c56a2bb 100644 --- a/src/path/Segment.js +++ b/src/path/Segment.js @@ -115,8 +115,7 @@ var Segment = Base.extend(/** @lends Segment# */{ */ initialize: function Segment(arg0, arg1, arg2, arg3, arg4, arg5) { var count = arguments.length, - point, handleIn, handleOut, - selection; + point, handleIn, handleOut; // TODO: Use Point.read or Point.readNamed to read these? if (count === 0) { // Nothing @@ -126,17 +125,17 @@ var Segment = Base.extend(/** @lends Segment# */{ point = arg0.point; handleIn = arg0.handleIn; handleOut = arg0.handleOut; - selection = arg0.selection; } else { point = arg0; } - } else if (typeof arg0 === 'object') { - // It doesn't matter if all of these arguments exist. - // new SegmentPoint() produces creates points with (0, 0) otherwise. + } else if (count === 2 && typeof arg0 === 'number') { + point = arguments; + } else if (count <= 3) { point = arg0; + // Doesn't matter if these arguments exist, SegmentPointcreate + // produces creates points with (0, 0) otherwise handleIn = arg1; handleOut = arg2; - selection = arg3; } else { // Read points from the arguments list as a row of numbers point = arg0 !== undefined ? [ arg0, arg1 ] : null; handleIn = arg2 !== undefined ? [ arg2, arg3 ] : null; @@ -145,8 +144,6 @@ var Segment = Base.extend(/** @lends Segment# */{ new SegmentPoint(point, this, '_point'); new SegmentPoint(handleIn, this, '_handleIn'); new SegmentPoint(handleOut, this, '_handleOut'); - if (selection) - this.setSelection(selection); }, _serialize: function(options, dictionary) { @@ -260,30 +257,8 @@ var Segment = Base.extend(/** @lends Segment# */{ this._handleOut.set(0, 0); }, - getSelection: function() { - return this._selection; - }, - - setSelection: function(selection) { - var oldSelection = this._selection, - path = this._path; - // Set the selection state even if path is not defined yet, to allow - // selected segments to be inserted into paths and make JSON - // deserialization work. - this._selection = selection = selection || 0; - // If the selection state of the segment has changed, we need to let - // it's path know and possibly add or remove it from - // project._selectionItems - if (path && selection !== oldSelection) { - path._updateSelection(this, oldSelection, selection); - // Let path know that we changed something and the view should be - // redrawn - path._changed(/*#=*/Change.ATTRIBUTE); - } - }, - - _getSelection: function(point) { - return !point ? /*#=*/SegmentSelection.ALL + _getSelectionFlag: function(point) { + return !point ? /*#=*/SegmentSelection.SEGMENT : point === this._point ? /*#=*/SegmentSelection.POINT : point === this._handleIn ? /*#=*/SegmentSelection.HANDLE_IN : point === this._handleOut ? /*#=*/SegmentSelection.HANDLE_OUT @@ -306,13 +281,33 @@ var Segment = Base.extend(/** @lends Segment# */{ * path.segments[2].selected = true; */ isSelected: function(_point) { - return !!(this._selection & this._getSelection(_point)); + return !!(this._selection & this._getSelectionFlag(_point)); }, setSelected: function(selected, _point) { - var selection = this._selection, - flag = this._getSelection(_point); - this.setSelection(selected ? selection | flag : selection & ~flag); + var path = this._path, + selected = !!selected, // convert to boolean + selection = this._selection, + oldSelection = selection, + flag = this._getSelectionFlag(_point); + if (selected) { + selection |= flag; + } else { + selection &= ~flag; + } + // Set the selection state even if path is not defined yet, to allow + // selected segments to be inserted into paths and make JSON + // deserialization work. + this._selection = selection; + // If the selection state of the segment has changed, we need to let + // it's path know and possibly add or remove it from + // project._selectedItems + if (path && selection !== oldSelection) { + path._updateSelection(this, oldSelection, selection); + // Let path know that we changed something and the view should be + // redrawn + path._changed(/*#=*/Change.ATTRIBUTE); + } }, /** diff --git a/src/path/SegmentPoint.js b/src/path/SegmentPoint.js index 303483d76f..83945d2b2b 100644 --- a/src/path/SegmentPoint.js +++ b/src/path/SegmentPoint.js @@ -18,14 +18,13 @@ */ var SegmentPoint = Point.extend({ initialize: function SegmentPoint(point, owner, key) { - var x, y, - selected; + var x, y, selected; if (!point) { x = y = 0; } else if ((x = point[0]) !== undefined) { // Array-like y = point[1]; } else { - // So we don't have to modify the point argument which would cause + // So we don't have to modify the point argument which causes // deoptimization: var pt = point; // If not Point-like already, read Point from arguments @@ -39,9 +38,9 @@ var SegmentPoint = Point.extend({ this._x = x; this._y = y; this._owner = owner; + // We have to set the owner's property that points to this point already + // now, so #setSelected(true) can work. owner[key] = this; - // We need to call #setSelected(true) after setting property on the - // owner that references this point. if (selected) this.setSelected(true); }, @@ -53,6 +52,15 @@ var SegmentPoint = Point.extend({ return this; }, + _serialize: function(options) { + var f = options.formatter, + x = f.number(this._x), + y = f.number(this._y); + return this.isSelected() + ? { x: x, y: y, selected: true } + : [x, y]; + }, + getX: function() { return this._x; }, diff --git a/src/path/SegmentSelection.js b/src/path/SegmentSelection.js index 559ef2949c..101bda6f2b 100644 --- a/src/path/SegmentSelection.js +++ b/src/path/SegmentSelection.js @@ -11,11 +11,11 @@ */ // Path#_segmentSelection is the addition of all segment's states, and is -// compared with SegmentSelection.ALL, the combination of all +// compared with SegmentSelection.SEGMENT, the combination of all // SegmentSelection values to see if all segments are fully selected. var SegmentSelection = { - POINT: 1, - HANDLE_IN: 2, - HANDLE_OUT: 4, - ALL: 1 | 2 | 4 // POINT | HANDLE_IN | HANDLE_OUT + HANDLE_IN: 1, + HANDLE_OUT: 2, + POINT: 4, + SEGMENT: 7 // HANDLE_IN | HANDLE_OUT | POINT };