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.

This commit is contained in:
Jürg Lehni 2012-12-30 18:24:33 +01:00
parent d0fff09bb0
commit 8bed8cb15d
3 changed files with 138 additions and 56 deletions

View file

@ -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.

View file

@ -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;
},

View file

@ -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);