From 8bed8cb15d7e148832441504bf517ff7b9de6b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sun, 30 Dec 2012 18:24:33 +0100 Subject: [PATCH] Define mechanism for flexible reading of named arguments through Base.readNamed() and Base.hasNamed(), and use it to implement property object literal versions of Path.Constructor code. --- src/core/Base.js | 65 +++++++++++++++++- src/item/Item.js | 8 ++- src/path/Path.Constructors.js | 121 ++++++++++++++++++++-------------- 3 files changed, 138 insertions(+), 56 deletions(-) diff --git a/src/core/Base.js b/src/core/Base.js index 67179b4d..9669e6b0 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -68,9 +68,9 @@ this.Base = Base.inject(/** @lends Base# */{ }, /** - * Checks if two values or objects are equals to each other, by using their - * equals() methods if available, and also comparing elements of arrays - * and properties of objects. + * Checks if two values or objects are equals to each other, by using + * their equals() methods if available, and also comparing elements of + * arrays and properties of objects. */ equals: function(obj1, obj2) { if (obj1 == obj2) @@ -117,6 +117,8 @@ this.Base = Base.inject(/** @lends Base# */{ * from the apssed arguments list or array. * This is used in argument conversion, e.g. by all basic types (Point, * Size, Rectangle) and also higher classes such as Color and Segment. + * @param {Array} list the list to read from, either an arguments object + * or a normal array. * @param {Number} start the index at which to start reading in the list * @param {Number} length the amount of elements that can be read * @param {Boolean} clone controls wether passed objects should be @@ -161,6 +163,13 @@ this.Base = Base.inject(/** @lends Base# */{ return obj; }, + /** + * Allows peeking ahead in reading of values and objects from arguments + * list through Base.read(). + * @param {Array} list the list to read from, either an arguments object + * or a normal array. + * @param {Number} start the index at which to start reading in the list + */ peek: function(list, start) { return list[list._index = start || list._index || 0]; }, @@ -168,6 +177,8 @@ this.Base = Base.inject(/** @lends Base# */{ /** * Reads all readable arguments from the list, handling nested arrays * seperately. + * @param {Array} list the list to read from, either an arguments object + * or a normal array. * @param {Number} start the index at which to start reading in the list * @param {Boolean} clone controls wether passed objects should be * cloned if they are already provided in the required type @@ -182,6 +193,54 @@ this.Base = Base.inject(/** @lends Base# */{ return res; }, + /** + * Allows using of Base.read() mechanism in combination with reading + * named arguments form a passed property object literal. Calling + * Base.readNamed() can read both from such named properties and normal + * unnamed arguments through Base.read(). In use for example for the + * various Path.Constructors. + * @param {Array} list the list to read from, either an arguments object + * or a normal array. + * @param {Number} start the index at which to start reading in the list + * @param {String} name the property name to read from. + * @param {Boolean} [filter=true] controls wether a clone of the passed + * object should be kept in list._filtered, of which the consumed + * properties are removed. This can be passed on e.g. to Item#set(). + */ + readNamed: function(list, name, filter) { + var value = this.getNamed(list, name), + // value is undefined if there is no arguments object, and null + // if there is one, but no value is defined. + hasObject = value !== undefined; + if (hasObject && filter !== false) { + if (!list._filtered) + list._filtered = Base.merge(list[0]); + delete list._filtered[name]; + } + return this.read(hasObject ? [value] : list); + }, + + /** + * @return the named value if the list provides an arguments object, + * null if the named value is null or undefined, and undefined if there + * is no arguments object. + */ + getNamed: function(list, name) { + var arg = list[0]; + if (list._hasObject === undefined) + list._hasObject = list.length === 1 && Base.isPlainObject(arg); + if (list._hasObject) { + value = arg[name]; + // Convert undefined to null, to distinguish from undefined + // result, when there is no arguments object. + return value !== undefined ? value : null; + } + }, + + hasNamed: function(list, name) { + return !!this.getNamed(list, name); + }, + /** * Serializes the passed object into a format that can be passed to * JSON.stringify() for JSON serialization. diff --git a/src/item/Item.js b/src/item/Item.js index 29805721..a4303aff 100644 --- a/src/item/Item.js +++ b/src/item/Item.js @@ -132,9 +132,11 @@ var Item = this.Item = Base.extend(Callback, /** @lends Item# */{ * the item it is called on, and returns the item itself. */ set: function(props) { - for (var key in props) - if (props.hasOwnProperty(key)) - this[key] = props[key]; + if (props) { + for (var key in props) + if (props.hasOwnProperty(key)) + this[key] = props[key]; + } return this; }, diff --git a/src/path/Path.Constructors.js b/src/path/Path.Constructors.js index 2c79d82e..b63cb2ac 100644 --- a/src/path/Path.Constructors.js +++ b/src/path/Path.Constructors.js @@ -16,13 +16,29 @@ Path.inject({ statics: new function() { - function createRectangle(rect) { - rect = Rectangle.read(arguments); - var left = rect.x, - top = rect.y, - right = left + rect.width, - bottom = top + rect.height, - path = new Path(); + function readRectangle(list) { + var rect; + if (Base.hasNamed(list, 'from')) { + rect = new Rectangle(Point.readNamed(list, 'from'), + Point.readNamed(list, 'to')); + } else if (Base.hasNamed(list, 'size')) { + rect = new Rectangle(Point.readNamed(list, 'point'), + Size.readNamed(list, 'size')); + if (Base.hasNamed(list, 'center')) + rect.setCenter(Point.readNamed(list, 'center')); + } else { + rect = Rectangle.readNamed(list, 'rectangle'); + } + return rect; + } + + function createRectangle() { + var rect = readRectangle(arguments), + left = rect.getLeft(), + top = rect.getTop(), + right = rect.getRight(), + bottom = rect.getBottom(), + path = new Path(arguments._filtered); path._add([ new Segment(Point.create(left, bottom)), new Segment(Point.create(left, top)), @@ -43,12 +59,13 @@ Path.inject({ statics: new function() { new Segment([0.5, 1], [kappa, 0 ], [-kappa, 0]) ]; - function createEllipse(rect) { - rect = Rectangle.read(arguments); - var path = new Path(), + function createEllipse() { + var rect = readRectangle(arguments), + path = new Path(arguments._filtered), point = rect.getPoint(true), size = rect.getSize(true), segments = new Array(4); + console.log(JSON.stringify(arguments._filtered)) for (var i = 0; i < 4; i++) { var segment = ellipseSegments[i]; segments[i] = new Segment( @@ -68,8 +85,8 @@ Path.inject({ statics: new function() { * * Creates a Path Item with two anchor points forming a line. * - * @param {Point} pt1 the first anchor point of the path - * @param {Point} pt2 the second anchor point of the path + * @param {Point} from the first anchor point of the path + * @param {Point} to the second anchor point of the path * @return {Path} the newly created path * * @example @@ -80,9 +97,9 @@ Path.inject({ statics: new function() { */ Line: function() { return new Path( - Point.read(arguments), - Point.read(arguments) - ); + Point.readNamed(arguments, 'from'), + Point.readNamed(arguments, 'to') + ).set(arguments._filtered); }, /** @@ -106,8 +123,8 @@ Path.inject({ statics: new function() { * constructor figures out how to fit a rectangle between them. * * @name Path.Rectangle - * @param {Point} point1 The first point defining the rectangle - * @param {Point} point2 The second point defining the rectangle + * @param {Point} from The first point defining the rectangle + * @param {Point} to The second point defining the rectangle * @return {Path} the newly created path * * @example @@ -120,7 +137,7 @@ Path.inject({ statics: new function() { * Creates a rectangle shaped Path Item from the passed abstract * {@link Rectangle}. * - * @param {Rectangle} rect + * @param {Rectangle} rectangle * @return {Path} the newly created path * * @example @@ -135,8 +152,8 @@ Path.inject({ statics: new function() { /** * Creates a rectangular Path Item with rounded corners. * - * @param {Rectangle} rect - * @param {Size} size the size of the rounded corners + * @param {Rectangle} rectangle + * @param {Size} radius the size of the rounded corners * @return {Path} the newly created path * * @example @@ -146,30 +163,30 @@ Path.inject({ statics: new function() { * var cornerSize = new Size(30, 30); * var path = new Path.RoundRectangle(rectangle, cornerSize); */ - RoundRectangle: function(rect, size) { - var _rect = Rectangle.read(arguments), - _size = Size.read(arguments); - if (_size.isZero()) + RoundRectangle: function(rect, radius) { + var _rect = Rectangle.readNamed(arguments, 'rectangle'), + _radius = Size.readNamed(arguments, 'radius'); + if (_radius.isZero()) return createRectangle(rect); - _size = Size.min(_size, _rect.getSize(true).divide(2)); + _radius = Size.min(_radius, _rect.getSize(true).divide(2)); var bl = _rect.getBottomLeft(true), tl = _rect.getTopLeft(true), tr = _rect.getTopRight(true), br = _rect.getBottomRight(true), - uSize = _size.multiply(kappa * 2), + h = _radius.multiply(kappa * 2), // handle vector path = new Path(); path._add([ - new Segment(bl.add(_size.width, 0), null, [-uSize.width, 0]), - new Segment(bl.subtract(0, _size.height), [0, uSize.height], null), + new Segment(bl.add(_radius.width, 0), null, [-h.width, 0]), + new Segment(bl.subtract(0, _radius.height), [0, h.height], null), - new Segment(tl.add(0, _size.height), null, [0, -uSize.height]), - new Segment(tl.add(_size.width, 0), [-uSize.width, 0], null), + new Segment(tl.add(0, _radius.height), null, [0, -h.height]), + new Segment(tl.add(_radius.width, 0), [-h.width, 0], null), - new Segment(tr.subtract(_size.width, 0), null, [uSize.width, 0]), - new Segment(tr.add(0, _size.height), [0, -uSize.height], null), + new Segment(tr.subtract(_radius.width, 0), null, [h.width, 0]), + new Segment(tr.add(0, _radius.height), [0, -h.height], null), - new Segment(br.subtract(0, _size.height), null, [0, uSize.height]), - new Segment(br.subtract(_size.width, 0), [uSize.width, 0], null) + new Segment(br.subtract(0, _radius.height), null, [0, h.height]), + new Segment(br.subtract(_radius.width, 0), [h.width, 0], null) ]); path._closed = true; return path; @@ -178,7 +195,7 @@ Path.inject({ statics: new function() { /** * Creates an ellipse shaped Path Item. * - * @param {Rectangle} rect + * @param {Rectangle} rectangle * @param {Boolean} [circumscribed=false] when set to {@code true} the * ellipse shaped path will be created so the rectangle fits into * it. When set to {@code false} the ellipse path will fit within @@ -210,10 +227,11 @@ Path.inject({ statics: new function() { * var path = new Path.Circle(new Point(100, 100), 50); */ Circle: function(center, radius) { - var _center = Point.read(arguments), - _radius = Base.read(arguments); + var _center = Point.readNamed(arguments, 'center'), + _radius = Base.readNamed(arguments, 'radius'); return createEllipse(new Rectangle(_center.subtract(_radius), - Size.create(_radius * 2, _radius * 2))); + Size.create(_radius * 2, _radius * 2))) + .set(arguments._filtered); }, /** @@ -232,9 +250,12 @@ Path.inject({ statics: new function() { * path.strokeColor = 'black'; */ Arc: function(from, through, to) { - var path = new Path(); - path.moveTo(from); - path.arcTo(through, to); + var _from = Point.readNamed(arguments, 'from'), + _through = Point.readNamed(arguments, 'through'), + _to = Point.readNamed(arguments, 'to'), + path = new Path(arguments._filtered); + path.moveTo(_from); + path.arcTo(_through, _to); return path; }, @@ -263,10 +284,10 @@ Path.inject({ statics: new function() { * decahedron.fillColor = 'black'; */ RegularPolygon: function(center, numSides, radius) { - var _center = Point.read(arguments), - _numSides = Base.read(arguments), - _radius = Base.read(arguments), - path = new Path(), + var _center = Point.readNamed(arguments, 'center'), + _numSides = Base.readNamed(arguments, 'numSides'), + _radius = Base.readNamed(arguments, 'radius'), + path = new Path(arguments._filtered), step = 360 / _numSides, three = !(_numSides % 3), vector = new Point(0, three ? -_radius : _radius), @@ -303,11 +324,11 @@ Path.inject({ statics: new function() { * path.fillColor = 'black'; */ Star: function(center, numPoints, radius1, radius2) { - var _center = Point.read(arguments), - _numPoints = Base.read(arguments) * 2, - _radius1 = Base.read(arguments), - _radius2 = Base.read(arguments), - path = new Path(), + var _center = Point.readNamed(arguments, 'center'), + _numPoints = Base.readNamed(arguments, 'numPoints') * 2, + _radius1 = Base.readNamed(arguments, 'radius1'), + _radius2 = Base.readNamed(arguments, 'radius2'), + path = new Path(arguments._filtered), step = 360 / _numPoints, vector = new Point(0, -1), segments = new Array(_numPoints);