diff --git a/examples/Tools/SquareRounded.html b/examples/Tools/SquareRounded.html index 1c47d5ab..9b4e6611 100644 --- a/examples/Tools/SquareRounded.html +++ b/examples/Tools/SquareRounded.html @@ -70,9 +70,7 @@ curHandleSeg = path.lastSegment; // clone as we want the unmodified one: prevPoint = curHandleSeg.point.clone(); - // TODO: potential bug - in SG we don't need to clone this - // point, why? - path.add(curHandleSeg.clone()); + path.add(curHandleSeg); curPoint = path.lastSegment.point; } } diff --git a/src/basic/Point.js b/src/basic/Point.js index 5eee82e6..dac0df42 100644 --- a/src/basic/Point.js +++ b/src/basic/Point.js @@ -159,7 +159,7 @@ var Point = this.Point = Base.extend({ if (this.isZero()) { if (this._angle != null) { var a = this._angle; - // Use #set() instead of direct assignment, so ObservedPoint + // Use #set() instead of direct assignment, so LinkedPoint // can optimise this.set( Math.cos(a) * length, @@ -177,7 +177,7 @@ var Point = this.Point = Base.extend({ // x and y are 0 this.getAngle(); } - // Use #set() instead of direct assignment, so ObservedPoint + // Use #set() instead of direct assignment, so LinkedPoint // can optimise this.set( this.x * scale, @@ -237,7 +237,7 @@ var Point = this.Point = Base.extend({ angle = this._angle = angle * Math.PI / 180; if (!this.isZero()) { var length = this.getLength(); - // Use #set() instead of direct assignment, so ObservedPoint + // Use #set() instead of direct assignment, so LinkedPoint // can optimise this.set( Math.cos(angle) * length, @@ -573,13 +573,19 @@ var Point = this.Point = Base.extend({ } }); -var ObservedPoint = Point.extend({ +/** + * An internal version of Point that notifies its owner of each change through + * setting itself again on the setter that corresponds to the getter that + * produced this LinkedPoint. See uses of LinkedPoint.create() + * Note: This prototype is not exported. + */ +var LinkedPoint = Point.extend({ beans: true, set: function(x, y) { this._x = x; this._y = y; - this._observer[this._set](this); + this._owner[this._set](this); return this; }, @@ -589,7 +595,7 @@ var ObservedPoint = Point.extend({ setX: function(x) { this._x = x; - this._observer[this._set](this); + this._owner[this._set](this); }, getY: function() { @@ -598,24 +604,15 @@ var ObservedPoint = Point.extend({ setY: function(y) { this._y = y; - this._observer[this._set](this); + this._owner[this._set](this); }, statics: { - /** - * Provide a faster creator for Points out of two coordinates that - * does not rely on Point#initialize at all. This speeds up all math - * operations a lot. - */ - create: function(observer, set, x, y) { - // Don't use the shorter form as we want absolute maximum - // performance here: - // return new Point(Point.dont).set(x, y); - // TODO: Benchmark and decide - var point = new ObservedPoint(ObservedPoint.dont); + create: function(owner, set, x, y) { + var point = new LinkedPoint(LinkedPoint.dont); point._x = x; point._y = y; - point._observer = observer; + point._owner = owner; point._set = set; return point; } diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js index 4dbdd10f..4af4ea14 100644 --- a/src/basic/Rectangle.js +++ b/src/basic/Rectangle.js @@ -71,7 +71,7 @@ var Rectangle = this.Rectangle = Base.extend({ }, getPoint: function() { - return ObservedPoint.create(this, 'setPoint', this.x, this.y); + return LinkedPoint.create(this, 'setPoint', this.x, this.y); }, setPoint: function(point) { @@ -97,7 +97,6 @@ var Rectangle = this.Rectangle = Base.extend({ }, setLeft: function(left) { - // right should not move this.width -= left - this.x; this.x = left; return this; @@ -150,7 +149,7 @@ var Rectangle = this.Rectangle = Base.extend({ }, getCenter: function() { - return ObservedPoint.create(this, 'setCenter', + return LinkedPoint.create(this, 'setCenter', this.getCenterX(), this.getCenterY()); }, @@ -255,19 +254,28 @@ var Rectangle = this.Rectangle = Base.extend({ getX = 'get' + x, getY = 'get' + y, setX = 'set' + x, - setY = 'set' + y; - this['get' + part] = function() { - return ObservedPoint.create(this, 'set' + part, + setY = 'set' + y, + get = 'get' + part, + set = 'set' + part; + this[get] = function() { + return LinkedPoint.create(this, set, this[getX](), this[getY]()); }; - this['set' + part] = function(point) { + this[set] = function(point) { point = Point.read(arguments); - return this[setX](point.x)[setY](point.y); // Note: call chaining! + // Note: call chaining happens here. + return this[setX](point.x)[setY](point.y); }; }, { beans: true }); }); -var ObservedRectangle = Rectangle.extend({ +/** + * An internal version of Rectangle that notifies its owner of each change + * through setting itself again on the setter that corresponds to the getter + * that produced this LinkedRectangle. See uses of LinkedRectangle.create() + * Note: This prototype is not exported. + */ +var LinkedRectangle = Rectangle.extend({ beans: true, set: function(x, y, width, height) { @@ -275,52 +283,8 @@ var ObservedRectangle = Rectangle.extend({ this._y = y; this._width = width; this._height = height; - if (this._observer) - this._observer[this._set](this); - return this; - }, - - // TODO: Use loop to create these? - getX: function() { - return this._x; - }, - - setX: function(x) { - this._x = x; - this._observer[this._set](this); - }, - - getY: function() { - return this._y; - }, - - setY: function(y) { - this._y = y; - this._observer[this._set](this); - }, - - getWidth: function() { - return this._width; - }, - - setWidth: function(width) { - this._width = width; - this._observer[this._set](this); - }, - - getHeight: function() { - return this._height; - }, - - setHeight: function(height) { - this._height = height; - this._observer[this._set](this); - }, - - // TODO: Implement for all properties on ObservedRectangle using loop - setCenter: function(center) { - Rectangle.prototype.setCenter.apply(this, center); - this._observer[this._set](this); + if (this._owner) + this._owner[this._set](this); return this; }, @@ -330,16 +294,47 @@ var ObservedRectangle = Rectangle.extend({ * does not rely on Point#initialize at all. This speeds up all math * operations a lot. */ - create: function(observer, set, x, y, width, height) { - // Don't use the shorter form as we want absolute maximum - // performance here: - // return new Point(Point.dont).set(x, y); - // TODO: Benchmark and decide - var rect = new ObservedRectangle(ObservedRectangle.dont).set(x, y, + create: function(owner, set, x, y, width, height) { + var rect = new LinkedRectangle(LinkedRectangle.dont).set(x, y, width, height); - rect._observer = observer; + rect._owner = owner; rect._set = set; return rect; } } +}, new function() { + var proto = Rectangle.prototype; + + return Base.each(['x', 'y', 'width', 'height'], function(key) { + var part = Base.capitalize(key); + var internal = '_' + key; + this['get' + part] = function() { + return this[internal]; + } + + this['set' + part] = function(value) { + this[internal] = value; + // Check if this setter is called from another one which sets + // _dontNotify, as it will notify itself + if (!this._dontNotify) + this._owner[this._set](this); + } + }, Base.each(['Point', 'Size', 'Center', + 'Left', 'Top', 'Right', 'Bottom', 'CenterX', 'CenterY', + 'TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', + 'LeftCenter', 'TopCenter', 'RightCenter', 'BottomCenter'], + function(key) { + var name = 'set' + key; + this[name] = function(value) { + // Make sure the above setters of x, y, width, height do not + // each notify the owner, as we're going to take care of this + // afterwards here, only once per change. + this._dontNotify = true; + proto[name].apply(this, arguments); + delete this._dontNotify; + this._owner[this._set](this); + return this; + } + }, { beans: true }) + ); }); diff --git a/src/item/Group.js b/src/item/Group.js index 2c7af3e8..f5b09e04 100644 --- a/src/item/Group.js +++ b/src/item/Group.js @@ -43,7 +43,7 @@ var Group = this.Group = Item.extend({ y2 = Math.max(rect2.y + rect2.height, y1 + y2 - y1); } } - return ObservedRectangle.create(this, 'setBounds', + return LinkedRectangle.create(this, 'setBounds', x1, y1, x2 - x1, y2 - y1); }, diff --git a/src/item/PlacedSymbol.js b/src/item/PlacedSymbol.js index a50e1f87..54789f98 100644 --- a/src/item/PlacedSymbol.js +++ b/src/item/PlacedSymbol.js @@ -44,7 +44,7 @@ var PlacedSymbol = this.PlacedSymbol = Item.extend({ getBounds: function() { var bounds = this.symbol._definition.getStrokeBounds(this.matrix, true); - return ObservedRectangle.create(this, 'setBounds', + return LinkedRectangle.create(this, 'setBounds', bounds.x, bounds.y, bounds.width, bounds.height); }, diff --git a/src/path/CompoundPath.js b/src/path/CompoundPath.js index a4672489..687c216e 100644 --- a/src/path/CompoundPath.js +++ b/src/path/CompoundPath.js @@ -42,7 +42,7 @@ var CompoundPath = this.CompoundPath = PathItem.extend({ y2 = Math.max(rect2.y + rect2.height, y1 + y2 - y1); } } - return ObservedRectangle.create(this, 'setBounds', + return LinkedRectangle.create(this, 'setBounds', x1, y1, x2 - x1, y2 - y1); }, diff --git a/src/path/Curve.js b/src/path/Curve.js index 9738eb50..1ee7d559 100644 --- a/src/path/Curve.js +++ b/src/path/Curve.js @@ -301,7 +301,7 @@ var Curve = this.Curve = Base.extend({ } } - // Amount of integral evaluations + // Amount of integral evaluations for the interval 0 <= a < b <= 1 function getIterations(a, b) { // Guess required precision based and size of range... // TODO: There should be much better educated guesses for diff --git a/src/path/Path.js b/src/path/Path.js index 26a43c36..e43c0b46 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -59,6 +59,15 @@ var Path = this.Path = PathItem.extend({ return this._segments[this._segments.length - 1]; }, + getFirstCurve: function() { + return this.getCurves()[0]; + }, + + getLastCurve: function() { + var curves = this.getCurves(); + return curves[curves - 1]; + }, + // TODO: Consider adding getSubPath(a, b), returning a part of the current // path, with the added benefit that b can be < a, and closed looping is // taken into account. @@ -701,7 +710,7 @@ var Path = this.Path = PathItem.extend({ // Pass the matrix hidden from Bootstrap, so it still inject // getBounds as bean too. var bounds = getBounds(this, arguments[0]); - return ObservedRectangle.create(this, 'setBounds', + return LinkedRectangle.create(this, 'setBounds', bounds.x, bounds.y, bounds.width, bounds.height); },