UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

554 lines (442 loc) 14.6 kB
/* ************************************************************************ 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) ************************************************************************ */ /** * A vertical box layout. * * The vertical box layout lays out widgets in a vertical column, from top * to bottom. * * *Features* * * * Minimum and maximum dimensions * * Prioritized growing/shrinking (flex) * * Margins (with vertical collapsing) * * Auto sizing (ignoring percent values) * * Percent heights (not relevant for size hint) * * Alignment (child property {@link qx.ui.core.LayoutItem#alignY} is ignored) * * Vertical spacing (collapsed with margins) * * Reversed children layout (from last to first) * * Horizontal children stretching (respecting size hints) * * *Item Properties* * * <ul> * <li><strong>flex</strong> <em>(Integer)</em>: The flexibility of a layout item determines how the container * distributes remaining empty space among its children. If items are made * flexible, they can grow or shrink accordingly. Their relative flex values * determine how the items are being resized, i.e. the larger the flex ratio * of two items, the larger the resizing of the first item compared to the * second. * * If there is only one flex item in a layout container, its actual flex * value is not relevant. To disallow items to become flexible, set the * flex value to zero. * </li> * <li><strong>height</strong> <em>(String)</em>: Allows to define a percent * height for the item. The height in percent, if specified, is used instead * of the height defined by the size hint. The minimum and maximum height still * takes care of the element's limits. It has no influence on the layout's * size hint. Percent values are mostly useful for widgets which are sized by * the outer hierarchy. * </li> * </ul> * * *Example* * * Here is a little example of how to use the vertical box layout. * * <pre class="javascript"> * var layout = new qx.ui.layout.VBox(); * layout.setSpacing(4); // apply spacing * * var container = new qx.ui.container.Composite(layout); * * container.add(new qx.ui.core.Widget()); * container.add(new qx.ui.core.Widget()); * container.add(new qx.ui.core.Widget()); * </pre> * * *External Documentation* * * See <a href='http://manual.qooxdoo.org/${qxversion}/pages/layout/box.html'>extended documentation</a> * and links to demos for this layout. * */ qx.Class.define("qx.ui.layout.VBox", { extend : qx.ui.layout.Abstract, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param spacing {Integer?0} The spacing between child widgets {@link #spacing}. * @param alignY {String?"top"} Vertical alignment of the whole children * block {@link #alignY}. * @param separator {String|qx.ui.decoration.IDecorator?} A separator to be rendered between the items */ construct : function(spacing, alignY, separator) { this.base(arguments); if (spacing) { this.setSpacing(spacing); } if (alignY) { this.setAlignY(alignY); } if (separator) { this.setSeparator(separator); } }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** * Vertical alignment of the whole children block. The vertical * alignment of the child is completely ignored in VBoxes ( * {@link qx.ui.core.LayoutItem#alignY}). */ alignY : { check : [ "top", "middle", "bottom" ], init : "top", apply : "_applyLayoutChange" }, /** * Horizontal alignment of each child. Can be overridden through * {@link qx.ui.core.LayoutItem#alignX}. */ alignX : { check : [ "left", "center", "right" ], init : "left", apply : "_applyLayoutChange" }, /** Vertical spacing between two children */ spacing : { check : "Integer", init : 0, apply : "_applyLayoutChange" }, /** Separator lines to use between the objects */ separator : { check : "Decorator", nullable : true, apply : "_applyLayoutChange" }, /** Whether the actual children list should be laid out in reversed order. */ reversed : { check : "Boolean", init : false, apply : "_applyReversed" } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { __heights : null, __flexs : null, __enableFlex : null, __children : null, /* --------------------------------------------------------------------------- HELPER METHODS --------------------------------------------------------------------------- */ // property apply _applyReversed : function() { // easiest way is to invalidate the cache this._invalidChildrenCache = true; // call normal layout change this._applyLayoutChange(); }, /** * Rebuilds caches for flex and percent layout properties */ __rebuildCache : function() { var children = this._getLayoutChildren(); var length = children.length; var enableFlex = false; var reuse = this.__heights && this.__heights.length != length && this.__flexs && this.__heights; var props; // Sparse array (keep old one if lengths has not been modified) var heights = reuse ? this.__heights : new Array(length); var flexs = reuse ? this.__flexs : new Array(length); // Reverse support if (this.getReversed()) { children = children.concat().reverse(); } // Loop through children to preparse values for (var i=0; i<length; i++) { props = children[i].getLayoutProperties(); if (props.height != null) { heights[i] = parseFloat(props.height) / 100; } if (props.flex != null) { flexs[i] = props.flex; enableFlex = true; } else { // reset (in case the index of the children changed: BUG #3131) flexs[i] = 0; } } // Store data if (!reuse) { this.__heights = heights; this.__flexs = flexs; } this.__enableFlex = enableFlex; this.__children = children; // Clear invalidation marker delete this._invalidChildrenCache; }, /* --------------------------------------------------------------------------- LAYOUT INTERFACE --------------------------------------------------------------------------- */ // overridden verifyLayoutProperty : qx.core.Environment.select("qx.debug", { "true" : function(item, name, value) { this.assert(name === "flex" || name === "height", "The property '"+name+"' is not supported by the VBox layout!"); if (name =="height") { this.assertMatch(value, qx.ui.layout.Util.PERCENT_VALUE); } else { // flex this.assertNumber(value); this.assert(value >= 0); } }, "false" : null }), // overridden renderLayout : function(availWidth, availHeight, padding) { // Rebuild flex/height caches if (this._invalidChildrenCache) { this.__rebuildCache(); } // Cache children var children = this.__children; var length = children.length; var util = qx.ui.layout.Util; // Compute gaps var spacing = this.getSpacing(); var separator = this.getSeparator(); if (separator) { var gaps = util.computeVerticalSeparatorGaps(children, spacing, separator); } else { var gaps = util.computeVerticalGaps(children, spacing, true); } // First run to cache children data and compute allocated height var i, child, height, percent; var heights = [], hint; var allocatedHeight = gaps; for (i=0; i<length; i+=1) { percent = this.__heights[i]; hint = children[i].getSizeHint(); height = percent != null ? Math.floor((availHeight - gaps) * percent) : hint.height; // Limit computed value if (height < hint.minHeight) { height = hint.minHeight; } else if (height > hint.maxHeight) { height = hint.maxHeight; } heights.push(height); allocatedHeight += height; } // Flex support (growing/shrinking) if (this.__enableFlex && allocatedHeight != availHeight) { var flexibles = {}; var flex, offset; for (i=0; i<length; i+=1) { flex = this.__flexs[i]; if (flex > 0) { hint = children[i].getSizeHint(); flexibles[i]= { min : hint.minHeight, value : heights[i], max : hint.maxHeight, flex : flex }; } } var result = util.computeFlexOffsets(flexibles, availHeight, allocatedHeight); for (i in result) { offset = result[i].offset; heights[i] += offset; allocatedHeight += offset; } } // Start with top coordinate var top = children[0].getMarginTop(); // Alignment support if (allocatedHeight < availHeight && this.getAlignY() != "top") { top = availHeight - allocatedHeight; if (this.getAlignY() === "middle") { top = Math.round(top / 2); } } // Layouting children var hint, left, width, height, marginBottom, marginLeft, marginRight; // Pre configure separators this._clearSeparators(); // Compute separator height if (separator) { var separatorInsets = qx.theme.manager.Decoration.getInstance().resolve(separator).getInsets(); var separatorHeight = separatorInsets.top + separatorInsets.bottom; } // Render children and separators for (i=0; i<length; i+=1) { child = children[i]; height = heights[i]; hint = child.getSizeHint(); marginLeft = child.getMarginLeft(); marginRight = child.getMarginRight(); // Find usable width width = Math.max(hint.minWidth, Math.min(availWidth-marginLeft-marginRight, hint.maxWidth)); // Respect horizontal alignment left = util.computeHorizontalAlignOffset(child.getAlignX()||this.getAlignX(), width, availWidth, marginLeft, marginRight); // Add collapsed margin if (i > 0) { // Whether a separator has been configured if (separator) { // add margin of last child and spacing top += marginBottom + spacing; // then render the separator at this position this._renderSeparator(separator, { top : top + padding.top, left : padding.left, height : separatorHeight, width : availWidth }); // and finally add the size of the separator, the spacing (again) and the top margin top += separatorHeight + spacing + child.getMarginTop(); } else { // Support margin collapsing when no separator is defined top += util.collapseMargins(spacing, marginBottom, child.getMarginTop()); } } // Layout child child.renderLayout(left + padding.left, top + padding.top, width, height); // Add height top += height; // Remember bottom margin (for collapsing) marginBottom = child.getMarginBottom(); } }, // overridden _computeSizeHint : function() { // Rebuild flex/height caches if (this._invalidChildrenCache) { this.__rebuildCache(); } var util = qx.ui.layout.Util; var children = this.__children; // Initialize var minHeight=0, height=0, percentMinHeight=0; var minWidth=0, width=0; var child, hint, margin; // Iterate over children for (var i=0, l=children.length; i<l; i+=1) { child = children[i]; hint = child.getSizeHint(); // Sum up heights height += hint.height; // Detect if child is shrinkable or has percent height and update minHeight var flex = this.__flexs[i]; var percent = this.__heights[i]; if (flex) { minHeight += hint.minHeight; } else if (percent) { percentMinHeight = Math.max(percentMinHeight, Math.round(hint.minHeight/percent)); } else { minHeight += hint.height; } // Build horizontal margin sum margin = child.getMarginLeft() + child.getMarginRight(); // Find biggest width if ((hint.width+margin) > width) { width = hint.width + margin; } // Find biggest minWidth if ((hint.minWidth+margin) > minWidth) { minWidth = hint.minWidth + margin; } } minHeight += percentMinHeight; // Respect gaps var spacing = this.getSpacing(); var separator = this.getSeparator(); if (separator) { var gaps = util.computeVerticalSeparatorGaps(children, spacing, separator); } else { var gaps = util.computeVerticalGaps(children, spacing, true); } // Return hint return { minHeight : minHeight + gaps, height : height + gaps, minWidth : minWidth, width : width }; } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { this.__heights = this.__flexs = this.__children = null; } });