From 49fca5510ed767fae87bfea373d73a66e6b7696e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sun, 19 Mar 2017 22:51:34 +0100 Subject: [PATCH] Improve handling of Rectangle dimension properties. Better deal with left / top / right / bottom / center values, and implement more unit tests for their behavior. Closes #1147 --- src/basic/Rectangle.js | 119 +++++++++++++-------- test/tests/Item_Bounds.js | 1 - test/tests/Rectangle.js | 218 ++++++++++++++++++++------------------ 3 files changed, 190 insertions(+), 148 deletions(-) diff --git a/src/basic/Rectangle.js b/src/basic/Rectangle.js index 040c3552..c97ce863 100644 --- a/src/basic/Rectangle.js +++ b/src/basic/Rectangle.js @@ -267,23 +267,34 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ return new ctor(this.width, this.height, this, 'setSize'); }, + // properties to keep track of fix-width / height: They are on by default, + // and switched off once properties are used that change the outside of the + // rectangle, so combinations of: left / top / right / bottom. + _fw: 1, + _fh: 1, + setSize: function(/* size */) { - var size = Size.read(arguments); - // Keep track of how dimensions were specified through this._fix* + var size = Size.read(arguments), + sx = this._sx, + sy = this._sy, + w = size.width, + h = size.height; + // Keep track of how dimensions were specified through this._s* // attributes. - // _fixX / Y can either be 0 (l), 0.5 (center) or 1 (r), and is used as - // direct factors to calculate the x / y adujstments from the size - // differences. - // _fixW / H is either 0 (off) or 1 (on), and is used to protect - // widht / height values against changes. - if (this._fixX) - this.x += (this.width - size.width) * this._fixX; - if (this._fixY) - this.y += (this.height - size.height) * this._fixY; - this.width = size.width; - this.height = size.height; - this._fixW = 1; - this._fixH = 1; + // _sx / _sy can either be 0 (left), 0.5 (center) or 1 (right), and is + // used as direct factors to calculate the x / y adjustments from the + // size differences. + // _fw / _fh can either be 0 (off) or 1 (on), and is used to protect + // width / height values against changes. + if (sx) { + this.x += (this.width - w) * sx; + } + if (sy) { + this.y += (this.height - h) * sy; + } + this.width = w; + this.height = h; + this._fw = this._fh = 1; }, /** @@ -300,10 +311,12 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, setLeft: function(left) { - if (!this._fixW) - this.width -= left - this.x; + if (!this._fw) { + var amount = left - this.x; + this.width -= this._sx === 0.5 ? amount * 2 : amount; + } this.x = left; - this._fixX = 0; + this._sx = this._fw = 0; }, /** @@ -318,10 +331,12 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, setTop: function(top) { - if (!this._fixH) - this.height -= top - this.y; + if (!this._fh) { + var amount = top - this.y; + this.height -= this._sy === 0.5 ? amount * 2 : amount; + } this.y = top; - this._fixY = 0; + this._sy = this._fh = 0; }, /** @@ -336,14 +351,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, setRight: function(right) { - // Turn _fixW off if we specify two _fixX values - if (this._fixX !== undefined && this._fixX !== 1) - this._fixW = 0; - if (this._fixW) - this.x = right - this.width; - else - this.width = right - this.x; - this._fixX = 1; + if (!this._fw) { + var amount = right - this.x; + this.width = this._sx === 0.5 ? amount * 2 : amount; + } + this.x = right - this.width; + this._sx = 1; + this._fw = 0; }, /** @@ -358,14 +372,13 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ }, setBottom: function(bottom) { - // Turn _fixH off if we specify two _fixY values - if (this._fixY !== undefined && this._fixY !== 1) - this._fixH = 0; - if (this._fixH) - this.y = bottom - this.height; - else - this.height = bottom - this.y; - this._fixY = 1; + if (!this._fh) { + var amount = bottom - this.y; + this.height = this._sy === 0.5 ? amount * 2 : amount; + } + this.y = bottom - this.height; + this._sy = 1; + this._fh = 0; }, /** @@ -376,12 +389,22 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * @ignore */ getCenterX: function() { - return this.x + this.width * 0.5; + return this.x + this.width / 2; }, setCenterX: function(x) { - this.x = x - this.width * 0.5; - this._fixX = 0.5; + // If we're asked to fix the width or if _sx is already in center mode, + // just keep moving the center. + if (this._fw || this._sx === 0.5) { + this.x = x - this.width / 2; + } else { + if (this._sx) { + this.x += (x - this.x) * 2 * this._sx; + } + this.width = (x - this.x) * 2; + } + this._sx = 0.5; + this._fw = 0; }, /** @@ -392,12 +415,22 @@ var Rectangle = Base.extend(/** @lends Rectangle# */{ * @ignore */ getCenterY: function() { - return this.y + this.height * 0.5; + return this.y + this.height / 2; }, setCenterY: function(y) { - this.y = y - this.height * 0.5; - this._fixY = 0.5; + // If we're asked to fix the height or if _sy is already in center mode, + // just keep moving the center. + if (this._fh || this._sy === 0.5) { + this.y = y - this.height / 2; + } else { + if (this._sy) { + this.y += (y - this.y) * 2 * this._sy; + } + this.height = (y - this.y) * 2; + } + this._sy = 0.5; + this._fh = 0; }, /** diff --git a/test/tests/Item_Bounds.js b/test/tests/Item_Bounds.js index 7cca697a..d6bf114a 100644 --- a/test/tests/Item_Bounds.js +++ b/test/tests/Item_Bounds.js @@ -207,7 +207,6 @@ test('path.bounds & path.strokeBounds with stroke styles', function() { } var path = makePath(); - path.fullySelected = true; path.strokeColor = 'black'; path.strokeCap = 'butt'; path.strokeJoin = 'round'; diff --git a/test/tests/Rectangle.js b/test/tests/Rectangle.js index 347532d7..af17a14d 100644 --- a/test/tests/Rectangle.js +++ b/test/tests/Rectangle.js @@ -14,127 +14,113 @@ QUnit.module('Rectangle'); test('new Rectangle(Point, Size);', function() { var rect = new Rectangle(new Point(10, 20), new Size(30, 40)); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); }); test('new Rectangle({ point, size });', function() { var rect = new Rectangle({ point: [10, 20], size: [30, 40] }); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); var rect = new Rectangle({ point: new Point(10, 20), size: new Size(30, 40)}); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); }); test('new Rectangle(Array, Array);', function() { var rect = new Rectangle([10, 20], [30, 40]); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); }); test('new Rectangle(Point, Point);', function() { var rect = new Rectangle(new Point(10, 20), new Point(30, 40)); - equals(rect, { x: 10, y: 20, width: 20, height: 20 }); + equals(rect, new Rectangle(10, 20, 20, 20)); }); test('new Rectangle({ from, to });', function() { var rect = new Rectangle({from: [10, 20], to: [30, 40]}); - equals(rect, { x: 10, y: 20, width: 20, height: 20 }); + equals(rect, new Rectangle(10, 20, 20, 20)); }); test('new Rectangle(x, y, width, height);', function() { var rect = new Rectangle(10, 20, 30, 40); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); }); test('new Rectangle({ x, y, width, height });', function() { var rect = new Rectangle({x: 10, y: 20, width: 30, height: 40}); - equals(rect, { x: 10, y: 20, width: 30, height: 40 }); + equals(rect, new Rectangle(10, 20, 30, 40)); }); test('new Rectangle(object)', function() { - equals(function() { - return new Rectangle({ - center: [50, 100], - size: [100, 200] - }); - }, { x: 0, y: 0, width: 100, height: 200 }); + var expected = new Rectangle(100, 50, 100, 200); equals(function() { return new Rectangle({ - topLeft: [100, 50], - size: [100, 200] + top: expected.top, + right: expected.right, + bottom: expected.bottom, + left: expected.left }); - }, { x: 100, y: 50, width: 100, height: 200 }); + }, expected); - equals(function() { - return new Rectangle({ - size: [100, 200], - topLeft: [100, 50] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); + function testProperties(key1, key2) { + var obj = {}; + obj[key1] = expected[key1]; + obj[key2] = expected[key2]; + var rect = new Rectangle(obj); + equals(rect, expected, 'new Rectangle({ ' + key1 + ', ' + key2 + ' });'); + } - equals(function() { - return new Rectangle({ - topRight: [200, 50], - size: [100, 200] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); + var tests = [ + ['center', 'size'], + ['topLeft', 'size'], + ['topRight', 'size'], + ['bottomRight', 'size'], + ['bottomLeft', 'size'], + ['leftCenter', 'size'], + ['topCenter', 'size'], + ['rightCenter', 'size'], + ['bottomCenter', 'size'], + ['topLeft', 'bottomRight'], + ['topRight', 'bottomLeft'], + ['topLeft', 'bottomCenter'], + ['topLeft', 'rightCenter'], + ['topRight', 'bottomCenter'], + ['topRight', 'leftCenter'], + ['bottomLeft', 'topCenter'], + ['bottomLeft', 'rightCenter'], + ['bottomRight', 'topCenter'], + ['bottomRight', 'leftCenter'] + ]; - equals(function() { - return new Rectangle({ - size: [100, 200], - topRight: [200, 50] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); + tests.forEach(function(test) { + testProperties(test[0], test[1]); + testProperties(test[1], test[0]); + }); +}); - equals(function() { - return new Rectangle({ - bottomRight: [200, 250], - size: [100, 200] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); +test('rect.left / rect.top VS rect.right / rect.bottom', function() { + var rect = new Rectangle({ + point: [0,0], + size: [100, 100], + }); + rect.left -= 10; + rect.top -= 10; + equals(rect.right, 90); + equals(rect.bottom, 90); - equals(function() { - return new Rectangle({ - size: [100, 200], - bottomRight: [200, 250] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); + var rect = new Rectangle([0, 0], [100, 100]); + rect.left -= 10; + rect.top -= 10; + equals(rect.right, 90); + equals(rect.bottom, 90); - equals(function() { - return new Rectangle({ - bottomLeft: [100, 250], - size: [100, 200] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); - - equals(function() { - return new Rectangle({ - size: [100, 200], - bottomLeft: [100, 250] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); - - equals(function() { - return new Rectangle({ - topRight: [200, 50], - bottomLeft: [100, 250] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); - - equals(function() { - return new Rectangle({ - topLeft: [100, 50], - bottomRight: [200, 250] - }); - }, { x: 100, y: 50, width: 100, height: 200 }); - - equals(function() { - return new Rectangle({ - top: 50, - right: 200, - bottom: 250, - left: 100 - }); - }, { x: 100, y: 50, width: 100, height: 200 }); + var rect = new Rectangle({ + topLeft: [0,0], + bottomRight: [100, 100], + }); + rect.left -= 10; + rect.top -= 10; + equals(rect.right, 100); + equals(rect.bottom, 100); }); test('rect.size', function() { @@ -142,8 +128,32 @@ test('rect.size', function() { equals(function() { return rect.size.equals([20, 30]); }, true); - rect.size = new Size(30, 40); - equals(rect, { x: 10, y: 10, width: 30, height: 40 }); + rect.size = [30, 40]; + equals(rect, new Rectangle(10, 10, 30, 40)); +}); + +test('rect.center', function() { + var rect = new Rectangle(10, 10, 20, 30); + equals(function() { + return rect.size; + }, new Size(20, 30)); + equals(function() { + return rect.center; + }, new Point(20, 25)); + rect.center = [100, 100]; + equals(function() { + return rect.center; + }, new Point(100, 100)); + equals(function() { + return rect.size; + }, new Size(20, 30)); + rect.center = [200, 200]; + equals(function() { + return rect.center; + }, new Point(200, 200)); + equals(function() { + return rect.size; + }, new Size(20, 30)); }); test('rect.topLeft', function() { @@ -226,32 +236,32 @@ test('rect.rightCenter', function() { }); test('rect1.intersects(rect2)', function() { - var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - var rect2 = { x: 195, y: 301, width: 19, height: 19 }; + var rect1 = new Rectangle(160, 270, 20, 20); + var rect2 = new Rectangle(195, 301, 19, 19); equals(function() { return rect1.intersects(rect2); }, false); - rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 }; + rect1 = new Rectangle(160, 270, 20, 20); + rect2 = new Rectangle(170.5, 280.5, 19, 19); equals(function() { return rect1.intersects(rect2); }, true); }); test('rect1.contains(rect2)', function() { - var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - var rect2 = { x: 195, y: 301, width: 19, height: 19 }; + var rect1 = new Rectangle(160, 270, 20, 20); + var rect2 = new Rectangle(195, 301, 19, 19); equals(function() { return rect1.contains(rect2); }, false); - rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - rect2 = new Rectangle({ x: 170.5, y: 280.5, width: 19, height: 19 }); + rect1 = new Rectangle(160, 270, 20, 20); + rect2 = new Rectangle(170.5, 280.5, 19, 19); equals(function() { return rect1.contains(rect2); }, false); - rect1 = new Rectangle({ x: 299, y: 161, width: 137, height: 129 }); - rect2 = new Rectangle({ x: 340, y: 197, width: 61, height: 61 }); + rect1 = new Rectangle(299, 161, 137, 129); + rect2 = new Rectangle(340, 197, 61, 61); equals(function() { return rect1.contains(rect2); }, true); @@ -261,7 +271,7 @@ test('rect1.contains(rect2)', function() { }); test('rect.contains(point)', function() { - var rect = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); + var rect = new Rectangle(160, 270, 20, 20); var point = new Point(166, 280); equals(function() { return rect.contains(point); @@ -273,28 +283,28 @@ test('rect.contains(point)', function() { }); test('rect1.intersect(rect2)', function() { - var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - var rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 }; + var rect1 = new Rectangle(160, 270, 20, 20); + var rect2 = new Rectangle(170.5, 280.5, 19, 19); var intersected = rect1.intersect(rect2); equals(function() { - return intersected.equals({ x: 170.5, y: 280.5, width: 9.5, height: 9.5 }); + return intersected.equals(new Rectangle(170.5, 280.5, 9.5, 9.5)); }, true); }); test('rect1.unite(rect2)', function() { - var rect1 = new Rectangle({ x: 160, y: 270, width: 20, height: 20 }); - var rect2 = { x: 170.5, y: 280.5, width: 19, height: 19 }; + var rect1 = new Rectangle(160, 270, 20, 20); + var rect2 = new Rectangle(170.5, 280.5, 19, 19); var united = rect1.unite(rect2); equals(function() { - return united.equals({ x: 160, y: 270, width: 29.5, height: 29.5 }); + return united.equals(new Rectangle(160, 270, 29.5, 29.5)); }, true); }); test('rect.include(point)', function() { - var rect1 = new Rectangle({ x: 95, y: 151, width: 20, height: 20 }); + var rect1 = new Rectangle(95, 151, 20, 20); var included = rect1.include([50, 50]); equals(function() { - return included.equals({ x: 50, y: 50, width: 65, height: 121 }); + return included.equals(new Rectangle(50, 50, 65, 121)); }, true); });