blossom
Version:
Modern, Cross-Platform Application Framework
547 lines (518 loc) • 18.8 kB
JavaScript
// ==========================================================================
// Project: Blossom - Modern, Cross-Platform Application Framework
// Copyright: ©2012 Fohr Motion Picture Studios. All rights reserved.
// License: Licensed under the GPLv3 license (see BLOSSOM-LICENSE).
// ==========================================================================
/*globals sc_assert */
sc_require('layers/layer');
SC.Layer.LAYOUT_X_PROPERTIES = 'left right centerX width'.w();
SC.Layer.LAYOUT_Y_PROPERTIES = 'top bottom centerY height'.w();
SC.Layer.LAYOUT_MINMAX_PROPERTIES = 'minLayoutWidth minLayoutHeight maxLayoutWidth maxLayoutHeight position'.w();
SC.Layer.LAYOUT_ALL_PROPERTIES = SC.Layer.LAYOUT_X_PROPERTIES.concat(SC.Layer.LAYOUT_Y_PROPERTIES, SC.Layer.LAYOUT_MINMAX_PROPERTIES);
SC.Layer.LAYOUT_POSITIONS = {
"center" : 0, "top" : 1, "bottom" : 2,
"left" : 3, "right" : 4, "top left" : 5,
"top right" : 6, "bottom left" : 7, "bottom right" : 8
};
SC.Layer.PERCENT_REGEX = /(([+\-]?\d+)|([+\-]?((\d*\.\d+)|(\d+\.\d*))))\%/;
SC.Layer.POSITIVE_PERCENT_REGEX = /(([+]?\d+)|([+]?((\d*\.\d+)|(\d+\.\d*))))\%/;
/**
This method takes a layout hash, verifies it's valid, and then updates
this._sc_layoutValues and this._sc_layoutFunction to match. Layout is
actually done later, during the animation loop.
*/
SC.Layer.prototype.updateLayoutRules = function() {
SC.Benchmark.start("SC.Layer#updateLayoutRules");
var layout = this.get('layout'),
values = this._sc_layoutValues,
isValid = true, key, val, ary, tmp = [], re, re2, percentages = {},
hmode, vmode, hmax, vmax, layoutMode;
if (typeof layout !== "object") {
throw new TypeError("layout must be a hash");
} else if (layout.isObject || layout.isClass) {
throw new TypeError("layout must not be a SproutCore object or class");
}
// First, make sure the hash doesn't contain any invalid keys.
ary = SC.Layer.LAYOUT_ALL_PROPERTIES;
for (key in layout) {
if (!layout.hasOwnProperty(key)) continue;
if (ary.indexOf(key) === -1) tmp.push(key);
}
if (tmp.length > 0) {
throw new TypeError("layout contains invalid keys: "+tmp);
}
// Next, make sure we have only two X properties and two Y properties.
ary = SC.Layer.LAYOUT_X_PROPERTIES;
for (key in layout) {
if (!layout.hasOwnProperty(key)) continue;
if (ary.indexOf(key) !== -1) tmp.push(key);
}
if (tmp.length > 2) {
throw new TypeError("layout has too many horizontal properties (it should have exactly two): "+tmp);
} else if (tmp.length < 2) {
throw new TypeError("layout has too few horizontal properties (it should have exactly two): "+tmp);
} else if (tmp.indexOf("centerX") !== -1) {
// only centerX, width is allowed
if (tmp.indexOf("width") === -1) {
throw new TypeError("centerX can only be combined with width in a layout hash");
}
}
tmp.length = 0;
ary = SC.Layer.LAYOUT_Y_PROPERTIES;
for (key in layout) {
if (!layout.hasOwnProperty(key)) continue;
if (ary.indexOf(key) !== -1) tmp.push(key);
}
if (tmp.length > 2) {
throw new TypeError("layout has too many vertical properties (it should have exactly two): "+tmp);
} else if (tmp.length < 2) {
throw new TypeError("layout has too few vertical properties (it should have exactly two): "+tmp);
} else if (tmp.indexOf("centerY") !== -1) {
// only centerY, height is allowed
if (tmp.indexOf("height") === -1) {
throw new TypeError("centerY can only be combined with height in a layout hash");
}
}
// If the position property is present, make sure it is valid. Also, cache
// it's value now.
if (layout.position !== undefined) {
layoutMode = SC.Layer.LAYOUT_POSITIONS[layout.position];
if (layoutMode === undefined) {
throw new TypeError("layout has an invalid position key: "+layout.position);
}
} else layoutMode = 5; // default is "top left" (for speed)
// At this point, we at least don't have any invalid keys, but it's also
// possible that the keys we do have don't contain good values. Verify the
// values, and convert any percentage strings into their floating-point
// equivalent. Also note which keys contain percentages.
re = SC.Layer.PERCENT_REGEX;
re2 = SC.Layer.POSITIVE_PERCENT_REGEX;
for (key in layout) {
if (!layout.hasOwnProperty(key)) continue;
val = layout[key];
switch (key) {
case "left":
case "right":
case "centerX":
case "top":
case "bottom":
case "centerY":
if (typeof val === "number") {
if (val > 0 && val < 1) percentages[key] = true;
} else if (typeof val === "string") {
if (!re.test(val)) {
throw new TypeError("layout."+key+" is not a percentage: "+val);
} else {
layout[key] = val.slice(0,-1)/100; // converts string to number
percentages[key] = true;
}
} else {
throw new TypeError("layout."+key+" must be either a number or a percentage. It's a "+typeof key);
}
break;
case "width":
case "height":
if (typeof val === "number" && val >= 0) {
if (val > 0 && val < 1) percentages[key] = true;
} else if (typeof val === "string") {
if (!re2.test(val)) {
throw new TypeError("layout."+key+" is not a positive percentage: "+val);
} else {
layout[key] = val.slice(0,-1)/100; // converts string to number
percentages[key] = true;
}
} else {
throw new TypeError("layout."+key+" must be either a number or a positive percentage. It's a "+typeof key);
}
break;
case "minLayoutWidth":
case "maxLayoutWidth":
case "minLayoutHeight":
case "maxLayoutHeight":
if (typeof val === "number") {
if (val >= 0) continue;
else {
throw new TypeError("layout."+key+" is not a positive number: "+val);
}
} else {
throw new TypeError("layout."+key+" must be a positive number. It's a "+typeof key);
}
break;
}
}
// Okay, the values for the keys supplied are okay, and any percentages
// have been converted. (If a key's value is a percentage, an entry has
// been made in the percentages hash.) For the next part, it's easiest if
// we know what kind of layout we're dealing with. That means we need to
// solve for `hmode`, `vmode`, `hmax`, and `vmax` (`layoutMode` we've
// already solved for).
// Solve for `hmode`.
if (layout.left !== undefined) {
if (percentages.left) {
if (layout.width !== undefined) {
if (percentages.width) {
// left percentage, width percentage
hmode = 3;
} else {
// left percentage, width
hmode = 1;
}
} else if (layout.right !== undefined) {
if (percentages.right) {
// left percentage, right percentage
hmode = 15;
} else {
// left percentage, right
hmode = 13;
}
}
} else {
if (layout.width !== undefined) {
if (percentages.width) {
// left, width percentage
hmode = 2;
} else {
// left, width
hmode = 0;
}
} else if (layout.right !== undefined) {
if (percentages.right) {
// left, right percentage
hmode = 14;
} else {
// left, right
hmode = 12;
}
}
}
} else if (layout.right !== undefined) {
if (percentages.right) {
if (layout.width !== undefined) {
if (percentages.width) {
// right percentage, width percentage
hmode = 7;
} else {
// right percentage, width
hmode = 5;
}
}
} else {
if (layout.width !== undefined) {
if (percentages.width) {
// right, width percentage
hmode = 6;
} else {
// right, width
hmode = 4;
}
}
}
} else if (layout.centerX !== undefined) {
if (percentages.centerX) {
if (layout.width !== undefined) {
if (percentages.width) {
// centerX percentage, width percentage
hmode = 11;
} else {
// centerX percentage, width
hmode = 9;
}
}
} else {
if (layout.width !== undefined) {
if (percentages.width) {
// centerX, width percentage
hmode = 10;
} else {
// centerX, width
hmode = 8;
}
}
}
}
// Solve for `vmode`.
if (layout.top !== undefined) {
if (percentages.top) {
if (layout.height !== undefined) {
if (percentages.height) {
// top percentage, height percentage
vmode = 3;
} else {
// top percentage, height
vmode = 1;
}
} else if (layout.bottom !== undefined) {
if (percentages.bottom) {
// top percentage, bottom percentage
vmode = 15;
} else {
// top percentage, bottom
vmode = 13;
}
}
} else {
if (layout.height !== undefined) {
if (percentages.height) {
// top, height percentage
vmode = 2;
} else {
// top, height
vmode = 0;
}
} else if (layout.bottom !== undefined) {
if (percentages.bottom) {
// top, bottom percentage
vmode = 14;
} else {
// top, bottom
vmode = 12;
}
}
}
} else if (layout.bottom !== undefined) {
if (percentages.bottom) {
if (layout.height !== undefined) {
if (percentages.height) {
// bottom percentage, height percentage
vmode = 7;
} else {
// bottom percentage, height
vmode = 5;
}
}
} else {
if (layout.height !== undefined) {
if (percentages.height) {
// bottom, height percentage
vmode = 6;
} else {
// bottom, height
vmode = 4;
}
}
}
} else if (layout.centerY !== undefined) {
if (percentages.centerY) {
if (layout.height !== undefined) {
if (percentages.width) {
// centerY percentage, height percentage
vmode = 11;
} else {
// centerY percentage, height
vmode = 9;
}
}
} else {
if (layout.height !== undefined) {
if (percentages.height) {
// centerY, height percentage
vmode = 10;
} else {
// centerY, height
vmode = 8;
}
}
}
}
// Solve for `hmax` and `vmax`.
hmax = layout.maxLayoutWidth !== undefined;
vmax = layout.maxLayoutHeight !== undefined;
// Time to update `values`.
switch (hmode) {
case 0: // left, width
case 2: // left, width percentage
case 12: // left, right
case 14: // left, right percentage
case 1: // left percentage, width
case 3: // left percentage, width percentage
case 13: // left percentage, right
case 15: // left percentage, right percentage
values[0]/*left OR left percentage*/ = layout.left;
break;
case 4: // right, width
case 6: // right, width percentage
case 5: // right percentage, width
case 7: // right percentage, width percentage
values[0]/*right OR right percentage*/ = layout.right;
break;
case 8: // centerX, width
case 10: // centerX, width percentage
case 9: // centerX percentage, width
case 11: // centerX percentage, width percentage
values[0]/*centerX OR centerX percentage*/ = layout.centerX;
break;
}
switch (hmode) {
case 0: // left, width
case 1: // left percentage, width
case 4: // right, width
case 5: // right percentage, width
case 8: // centerX, width
case 9: // centerX percentage, width
case 2: // left, width percentage
case 3: // left percentage, width percentage
case 6: // right, width percentage
case 7: // right percentage, width percentage
case 10: // centerX, width percentage
case 11: // centerX percentage, width percentage
values[1]/*width OR width percentage*/ = layout.width;
break;
// Each of the following cases require both the first and second
// value.
case 12: // left, right
case 13: // left percentage, right
case 14: // left, right percentage
case 15: // left percentage, right percentage
values[1]/*right OR right percentage*/ = layout.right;
break;
}
switch (vmode) {
case 0: // top, height
case 2: // top, height percentage
case 12: // top, bottom
case 14: // top, bottom percentage
case 1: // top percentage, height
case 3: // top percentage, height percentage
case 13: // top percentage, bottom
case 15: // top percentage, bottom percentage
values[2]/*top OR top percentage*/ = layout.top;
break;
case 4: // bottom, height
case 6: // bottom, height percentage
case 5: // bottom percentage, height
case 7: // bottom percentage, height percentage
values[2]/*bottom OR bottom percentage*/ = layout.bottom;
break;
case 8: // centerY, height
case 10: // centerY, height percentage
case 9: // centerY percentage, height
case 11: // centerY percentage, height percentage
values[2]/*centerY OR centerY percentage*/ = layout.centerY;
break;
}
switch (vmode) {
case 0: // top, height
case 1: // top percentage, height
case 4: // bottom, height
case 5: // bottom prcentage, height
case 8: // centerY, height
case 9: // centerY percentage, height
case 2: // top, height percentage
case 3: // top percentage, height percentage
case 6: // bottom, height percentage
case 7: // bottom percentage, height percentage
case 10: // centerY, height percentage
case 11: // centerY percentage, height percentage
values[3]/*height OR height percentage*/ = layout.height;
break;
// Each of the following cases require both the first and second value.
case 12: // top, bottom
case 13: // top percentage, bottom
case 14: // top, bottom percentage
case 15: // top percentage, bottom percentage
values[3]/*bottom OR bottom percentage*/ = layout.bottom;
break;
}
// Okay, time to deal with the min/max limits. minLayoutWidth depends on
// hmode.
var minLayoutWidth, maxLayoutWidth, minLayoutHeight, maxLayoutHeight;
switch (hmode) {
case 0: // left, width
minLayoutWidth = layout.left + layout.width;
break;
case 1: // left percentage, width
case 5: // right percentage, width
case 8: // centerX, width
case 9: // centerX percentage, width
minLayoutWidth = layout.width;
break;
case 2: // left, width percentage
case 14: // left, right percentage
minLayoutWidth = layout.left;
break;
case 3: // left percentage, width percentage
case 7: // right percentage, width percentage
case 10: // centerX, width percentage
case 11: // centerX percentage, width percentage
case 15: // left percentage, right percentage
minLayoutWidth = 0;
break;
case 4: // right, width
minLayoutWidth = layout.right + layout.width;
break;
case 6: // right, width percentage
case 13: // left percentage, right
minLayoutWidth = layout.right;
break;
case 12: // left, right
minLayoutWidth = layout.left + layout.right;
break;
}
if (layout.minLayoutWidth !== undefined) {
if (layout.minLayoutWidth > minLayoutWidth) {
minLayoutWidth = layout.minLayoutWidth;
}
}
values[4] = minLayoutWidth;
if (layout.maxLayoutWidth !== undefined) {
if (layout.maxLayoutWidth >= minLayoutWidth) {
maxLayoutWidth = layout.maxLayoutWidth;
} else {
maxLayoutWidth = minLayoutWidth;
}
} else {
maxLayoutWidth = Infinity;
}
values[5] = maxLayoutWidth;
switch (vmode) {
case 0: // top, height
minLayoutHeight = layout.top + layout.height;
break;
case 1: // top percentage, height
case 5: // bottom percentage, height
case 8: // centerY, height
case 9: // centerY percentage, height
minLayoutHeight = layout.height;
break;
case 2: // top, height percentage
case 14: // top, bottom percentage
minLayoutHeight = layout.top;
break;
case 3: // top percentage, height percentage
case 7: // bottom percentage, height percentage
case 10: // centerY, height percentage
case 11: // centerY percentage, height percentage
case 15: // top percentage, bottom percentage
minLayoutHeight = 0;
break;
case 4: // bottom, height
minLayoutHeight = layout.bottom + layout.height;
break;
case 6: // bottom, height percentage
case 13: // top percentage, bottom
minLayoutHeight = layout.bottom;
break;
case 12: // top, bottom
minLayoutHeight = layout.top + layout.bottom;
break;
}
if (layout.minLayoutHeight !== undefined) {
if (layout.minLayoutHeight > minLayoutHeight) {
minLayoutHeight = layout.minLayoutHeight;
}
}
values[6] = minLayoutHeight;
if (layout.maxLayoutHeight !== undefined) {
if (layout.maxLayoutHeight >= minLayoutHeight) {
maxLayoutHeight = layout.maxLayoutHeight;
} else {
maxLayoutHeight = minLayoutHeight;
}
} else {
maxLayoutHeight = Infinity;
}
values[7] = maxLayoutHeight;
// Now that values is fully updated, assign the correct layout function.
this._sc_layoutFunction = SC.GetLayoutFunction(hmode, vmode, hmax, vmax, layoutMode);
SC.Benchmark.end("SC.Layer#updateLayoutRules");
};