@qooxdoo/framework
Version:
The JS Framework for Coders
390 lines (332 loc) • 12.5 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* The Canvas is an extended Basic layout.
*
* It is possible to position a widget relative to the right or bottom edge of
* the available space. It further supports stretching between left and right
* or top and bottom e.g. <code>left=20</code> and <code>right=20</code> would
* keep a margin of 20 pixels to both edges. The Canvas layout has support for
* percent dimensions and locations.
*
* *Features*
*
* * Pixel dimensions and locations
* * Percent dimensions and locations
* * Stretching between left+right and top+bottom
* * Minimum and maximum dimensions
* * Children are automatically shrunk to their minimum dimensions if not enough space is available
* * Auto sizing (ignoring percent values)
* * Margins (also negative ones)
*
* *Item Properties*
*
* <ul>
* <li><strong>left</strong> <em>(Integer|String)</em>: The left coordinate in pixel or as a percent string e.g. <code>20</code> or <code>30%</code>.</li>
* <li><strong>top</strong> <em>(Integer|String)</em>: The top coordinate in pixel or as a percent string e.g. <code>20</code> or <code>30%</code>.</li>
* <li><strong>right</strong> <em>(Integer|String)</em>: The right coordinate in pixel or as a percent string e.g. <code>20</code> or <code>30%</code>.</li>
* <li><strong>bottom</strong> <em>(Integer|String)</em>: The bottom coordinate in pixel or as a percent string e.g. <code>20</code> or <code>30%</code>.</li>
* <li><strong>edge</strong> <em>(Integer|String)</em>: The coordinate in pixels or as a percent string to be used for all four edges.
* <li><strong>width</strong> <em>(String)</em>: A percent width e.g. <code>40%</code>.</li>
* <li><strong>height</strong> <em>(String)</em>: A percent height e.g. <code>60%</code>.</li>
* </ul>
*
* *Notes*
*
* <ul>
* <li>Stretching (<code>left</code>-><code>right</code> or <code>top</code>-><code>bottom</code>)
* has a higher priority than the preferred dimensions</li>
* <li>Stretching has a lower priority than the min/max dimensions.</li>
* <li>Percent values have no influence on the size hint of the layout.</li>
* </ul>
*
* *Example*
*
* Here is a little example of how to use the canvas layout.
*
* <pre class="javascript">
* var container = new qx.ui.container.Composite(new qx.ui.layout.Canvas());
*
* // simple positioning
* container.add(new qx.ui.core.Widget(), {top: 10, left: 10});
*
* // stretch vertically with 10 pixel distance to the parent's top
* // and bottom border
* container.add(new qx.ui.core.Widget(), {top: 10, left: 10, bottom: 10});
*
* // percent positioning and size
* container.add(new qx.ui.core.Widget(), {left: "50%", top: "50%", width: "25%", height: "40%"});
* </pre>
*
* *External Documentation*
*
* <a href='https://qooxdoo.org/documentation/#/desktop/layout/canvas.md'>
* Extended documentation</a> and links to demos of this layout in the qooxdoo manual.
*/
qx.Class.define("qx.ui.layout.Canvas", {
extend: qx.ui.layout.Abstract,
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
/**
* If desktop mode is active, the children's minimum sizes are ignored
* by the layout calculation. This is necessary to prevent the desktop
* from growing if e.g. a window is moved beyond the edge of the desktop
*/
desktop: {
check: "Boolean",
init: false
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
/*
---------------------------------------------------------------------------
LAYOUT INTERFACE
---------------------------------------------------------------------------
*/
// overridden
verifyLayoutProperty: qx.core.Environment.select("qx.debug", {
true(item, name, value) {
var layoutProperties = {
top: 1,
left: 1,
bottom: 1,
right: 1,
width: 1,
height: 1,
edge: 1
};
this.assert(
layoutProperties[name] == 1,
"The property '" + name + "' is not supported by the Canvas layout!"
);
if (name == "width" || name == "height") {
this.assertMatch(value, qx.ui.layout.Util.PERCENT_VALUE);
} else {
if (typeof value === "number") {
this.assertInteger(value);
} else if (qx.lang.Type.isString(value)) {
this.assertMatch(value, qx.ui.layout.Util.PERCENT_VALUE);
} else {
this.fail(
"Bad format of layout property '" +
name +
"': " +
value +
". The value must be either an integer or an percent string."
);
}
}
},
false: null
}),
// overridden
renderLayout(availWidth, availHeight, padding) {
var children = this._getLayoutChildren();
var child, size, props;
var left, top, right, bottom, width, height;
var marginTop, marginRight, marginBottom, marginLeft;
for (var i = 0, l = children.length; i < l; i++) {
child = children[i];
size = child.getSizeHint();
props = child.getLayoutProperties();
// Cache margins
marginTop = child.getMarginTop();
marginRight = child.getMarginRight();
marginBottom = child.getMarginBottom();
marginLeft = child.getMarginLeft();
// **************************************
// Processing location
// **************************************
left = props.left != null ? props.left : props.edge;
if (qx.lang.Type.isString(left)) {
left = Math.round((parseFloat(left) * availWidth) / 100);
}
right = props.right != null ? props.right : props.edge;
if (qx.lang.Type.isString(right)) {
right = Math.round((parseFloat(right) * availWidth) / 100);
}
top = props.top != null ? props.top : props.edge;
if (qx.lang.Type.isString(top)) {
top = Math.round((parseFloat(top) * availHeight) / 100);
}
bottom = props.bottom != null ? props.bottom : props.edge;
if (qx.lang.Type.isString(bottom)) {
bottom = Math.round((parseFloat(bottom) * availHeight) / 100);
}
// **************************************
// Processing dimension
// **************************************
// Stretching has higher priority than dimension data
if (left != null && right != null) {
width = availWidth - left - right - marginLeft - marginRight;
// Limit computed value
if (width < size.minWidth) {
width = size.minWidth;
} else if (width > size.maxWidth) {
width = size.maxWidth;
}
// Add margin
left += marginLeft;
} else {
// Layout data has higher priority than data from size hint
width = props.width;
if (width == null) {
width = size.width;
} else {
width = Math.round((parseFloat(width) * availWidth) / 100);
// Limit computed value
if (width < size.minWidth) {
width = size.minWidth;
} else if (width > size.maxWidth) {
width = size.maxWidth;
}
}
// AlignX support.
if (left == null && right == null) {
switch (child.getAlignX()) {
case "center":
left = Math.round((availWidth - size.width) / 2 - marginRight);
break;
case "right":
right = 0;
break;
}
}
if (right != null) {
left = availWidth - width - right - marginRight;
} else if (left == null) {
left = marginLeft;
} else {
left += marginLeft;
}
}
// Stretching has higher priority than dimension data
if (top != null && bottom != null) {
height = availHeight - top - bottom - marginTop - marginBottom;
// Limit computed value
if (height < size.minHeight) {
height = size.minHeight;
} else if (height > size.maxHeight) {
height = size.maxHeight;
}
// Add margin
top += marginTop;
} else {
// Layout data has higher priority than data from size hint
height = props.height;
if (height == null) {
height = size.height;
} else {
height = Math.round((parseFloat(height) * availHeight) / 100);
// Limit computed value
if (height < size.minHeight) {
height = size.minHeight;
} else if (height > size.maxHeight) {
height = size.maxHeight;
}
}
// AlignY support.
if (top == null && bottom == null) {
switch (child.getAlignY()) {
case "middle":
top = Math.round(
(availHeight - size.height) / 2 - marginBottom
);
break;
case "bottom":
bottom = 0;
break;
}
}
if (bottom != null) {
top = availHeight - height - bottom - marginBottom;
} else if (top == null) {
top = marginTop;
} else {
top += marginTop;
}
}
left += padding.left;
top += padding.top;
// Apply layout
child.renderLayout(left, top, width, height);
}
},
// overridden
_computeSizeHint() {
var neededWidth = 0,
neededMinWidth = 0;
var neededHeight = 0,
neededMinHeight = 0;
var width, minWidth;
var height, minHeight;
var children = this._getLayoutChildren();
var child, props, hint;
var desktop = this.isDesktop();
var left, top, right, bottom;
for (var i = 0, l = children.length; i < l; i++) {
child = children[i];
props = child.getLayoutProperties();
hint = child.getSizeHint();
// Cache margins
var marginX = child.getMarginLeft() + child.getMarginRight();
var marginY = child.getMarginTop() + child.getMarginBottom();
// Compute width
width = hint.width + marginX;
minWidth = hint.minWidth + marginX;
left = props.left != null ? props.left : props.edge;
if (left && typeof left === "number") {
width += left;
minWidth += left;
}
right = props.right != null ? props.right : props.edge;
if (right && typeof right === "number") {
width += right;
minWidth += right;
}
neededWidth = Math.max(neededWidth, width);
neededMinWidth = desktop ? 0 : Math.max(neededMinWidth, minWidth);
// Compute height
height = hint.height + marginY;
minHeight = hint.minHeight + marginY;
top = props.top != null ? props.top : props.edge;
if (top && typeof top === "number") {
height += top;
minHeight += top;
}
bottom = props.bottom != null ? props.bottom : props.edge;
if (bottom && typeof bottom === "number") {
height += bottom;
minHeight += bottom;
}
neededHeight = Math.max(neededHeight, height);
neededMinHeight = desktop ? 0 : Math.max(neededMinHeight, minHeight);
}
return {
width: neededWidth,
minWidth: neededMinWidth,
height: neededHeight,
minHeight: neededMinHeight
};
}
}
});