UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

514 lines (431 loc) 15.5 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) ************************************************************************ */ /** * Common set of utility methods used by the standard qooxdoo layouts. * * @internal */ qx.Class.define("qx.ui.layout.Util", { statics : { /** @type {RegExp} Regular expression to match percent values */ PERCENT_VALUE : /[0-9]+(?:\.[0-9]+)?%/, /** * Computes the flex offsets needed to reduce the space * difference as much as possible by respecting the * potential of the given elements (being in the range of * their min/max values) * * @param flexibles {Map} Each entry must have these keys: * <code>id</code>, <code>potential</code> and <code>flex</code>. * The ID is used in the result map as the key for the user to work * with later (e.g. upgrade sizes etc. to respect the given offset) * The potential is an integer value which is the difference of the * currently interesting direction (e.g. shrinking=width-minWidth, growing= * maxWidth-width). The flex key holds the flex value of the item. * @param avail {Integer} Full available space to allocate (ignoring used one) * @param used {Integer} Size of already allocated space * @return {Map} A map which contains the calculated offsets under the key * which is identical to the ID given in the incoming map. */ computeFlexOffsets : function(flexibles, avail, used) { var child, key, flexSum, flexStep; var grow = avail > used; var remaining = Math.abs(avail - used); var roundingOffset, currentOffset; // Preprocess data var result = {}; for (key in flexibles) { child = flexibles[key]; result[key] = { potential : grow ? child.max - child.value : child.value - child.min, flex : grow ? child.flex : 1 / child.flex, offset : 0 }; } // Continue as long as we need to do anything while (remaining != 0) { // Find minimum potential for next correction flexStep = Infinity; flexSum = 0; for (key in result) { child = result[key]; if (child.potential > 0) { flexSum += child.flex; flexStep = Math.min(flexStep, child.potential / child.flex); } } // No potential found, quit here if (flexSum == 0) { break; } // Respect maximum potential given through remaining space // The parent should always win in such conflicts. flexStep = Math.min(remaining, flexStep * flexSum) / flexSum; // Start with correction roundingOffset = 0; for (key in result) { child = result[key]; if (child.potential > 0) { // Compute offset for this step currentOffset = Math.min(remaining, child.potential, Math.ceil(flexStep * child.flex)); // Fix rounding issues roundingOffset += currentOffset - flexStep * child.flex; if (roundingOffset >= 1) { roundingOffset -= 1; currentOffset -= 1; } // Update child status child.potential -= currentOffset; if (grow) { child.offset += currentOffset; } else { child.offset -= currentOffset; } // Update parent status remaining -= currentOffset; } } } return result; }, /** * Computes the offset which needs to be added to the top position * to result in the stated vertical alignment. Also respects * existing margins (without collapsing). * * @param align {String} One of <code>top</code>, <code>center</code> or <code>bottom</code>. * @param width {Integer} The visible width of the widget * @param availWidth {Integer} The available inner width of the parent * @param marginLeft {Integer?0} Optional left margin of the widget * @param marginRight {Integer?0} Optional right margin of the widget * @return {Integer} Computed top coordinate */ computeHorizontalAlignOffset : function(align, width, availWidth, marginLeft, marginRight) { if (marginLeft == null) { marginLeft = 0; } if (marginRight == null) { marginRight = 0; } var value = 0; switch(align) { case "left": value = marginLeft; break; case "right": // Align right changes priority to right edge: // To align to the right is more important here than to left. value = availWidth - width - marginRight; break; case "center": // Ideal center position value = Math.round((availWidth - width) / 2); // Try to make this possible (with left-right priority) if (value < marginLeft) { value = marginLeft; } else if (value < marginRight) { value = Math.max(marginLeft, availWidth-width-marginRight); } break; } return value; }, /** * Computes the offset which needs to be added to the top position * to result in the stated vertical alignment. Also respects * existing margins (without collapsing). * * @param align {String} One of <code>top</code>, <code>middle</code> or <code>bottom</code>. * @param height {Integer} The visible height of the widget * @param availHeight {Integer} The available inner height of the parent * @param marginTop {Integer?0} Optional top margin of the widget * @param marginBottom {Integer?0} Optional bottom margin of the widget * @return {Integer} Computed top coordinate */ computeVerticalAlignOffset : function(align, height, availHeight, marginTop, marginBottom) { if (marginTop == null) { marginTop = 0; } if (marginBottom == null) { marginBottom = 0; } var value = 0; switch(align) { case "top": value = marginTop; break; case "bottom": // Align bottom changes priority to bottom edge: // To align to the bottom is more important here than to top. value = availHeight - height - marginBottom; break; case "middle": // Ideal middle position value = Math.round((availHeight - height) / 2); // Try to make this possible (with top-down priority) if (value < marginTop) { value = marginTop; } else if (value < marginBottom) { value = Math.max(marginTop, availHeight-height-marginBottom); } break; } return value; }, /** * Collapses two margins. * * Supports positive and negative margins. * Collapsing find the largest positive and the largest * negative value. Afterwards the result is computed through the * subtraction of the negative from the positive value. * * @param varargs {arguments} Any number of configured margins * @return {Integer} The collapsed margin */ collapseMargins : function(varargs) { var max=0, min=0; for (var i=0, l=arguments.length; i<l; i++) { var value = arguments[i]; if (value < 0) { min = Math.min(min, value); } else if (value > 0) { max = Math.max(max, value); } } return max + min; }, /** * Computes the sum of all horizontal gaps. Normally the * result is used to compute the available width in a widget. * * The method optionally respects margin collapsing as well. In * this mode the spacing is collapsed together with the margins. * * @param children {Array} List of children * @param spacing {Integer?0} Spacing between every child * @param collapse {Boolean?false} Optional margin collapsing mode * @return {Integer} Sum of all gaps in the final layout. */ computeHorizontalGaps : function(children, spacing, collapse) { if (spacing == null) { spacing = 0; } var gaps = 0; if (collapse) { // Add first child gaps += children[0].getMarginLeft(); for (var i=1, l=children.length; i<l; i+=1) { gaps += this.collapseMargins(spacing, children[i-1].getMarginRight(), children[i].getMarginLeft()); } // Add last child gaps += children[l-1].getMarginRight(); } else { // Simple adding of all margins for (var i=1, l=children.length; i<l; i+=1) { gaps += children[i].getMarginLeft() + children[i].getMarginRight(); } // Add spacing gaps += (spacing * (l-1)); } return gaps; }, /** * Computes the sum of all vertical gaps. Normally the * result is used to compute the available height in a widget. * * The method optionally respects margin collapsing as well. In * this mode the spacing is collapsed together with the margins. * * @param children {Array} List of children * @param spacing {Integer?0} Spacing between every child * @param collapse {Boolean?false} Optional margin collapsing mode * @return {Integer} Sum of all gaps in the final layout. */ computeVerticalGaps : function(children, spacing, collapse) { if (spacing == null) { spacing = 0; } var gaps = 0; if (collapse) { // Add first child gaps += children[0].getMarginTop(); for (var i=1, l=children.length; i<l; i+=1) { gaps += this.collapseMargins(spacing, children[i-1].getMarginBottom(), children[i].getMarginTop()); } // Add last child gaps += children[l-1].getMarginBottom(); } else { // Simple adding of all margins for (var i=1, l=children.length; i<l; i+=1) { gaps += children[i].getMarginTop() + children[i].getMarginBottom(); } // Add spacing gaps += (spacing * (l-1)); } return gaps; }, /** * Computes the gaps together with the configuration of separators. * * @param children {qx.ui.core.LayoutItem[]} List of children * @param spacing {Integer} Configured spacing * @param separator {String|qx.ui.decoration.IDecorator} Separator to render * @return {Integer} Sum of gaps */ computeHorizontalSeparatorGaps : function(children, spacing, separator) { var instance = qx.theme.manager.Decoration.getInstance().resolve(separator); var insets = instance.getInsets(); var width = insets.left + insets.right; var gaps = 0; for (var i=0, l=children.length; i<l; i++) { var child = children[i]; gaps += child.getMarginLeft() + child.getMarginRight(); } gaps += (spacing + width + spacing) * (l-1); return gaps; }, /** * Computes the gaps together with the configuration of separators. * * @param children {qx.ui.core.LayoutItem[]} List of children * @param spacing {Integer} Configured spacing * @param separator {String|qx.ui.decoration.IDecorator} Separator to render * @return {Integer} Sum of gaps */ computeVerticalSeparatorGaps : function(children, spacing, separator) { var instance = qx.theme.manager.Decoration.getInstance().resolve(separator); var insets = instance.getInsets(); var height = insets.top + insets.bottom; var gaps = 0; for (var i=0, l=children.length; i<l; i++) { var child = children[i]; gaps += child.getMarginTop() + child.getMarginBottom(); } gaps += (spacing + height + spacing) * (l-1); return gaps; }, /** * Arranges two sizes in one box to best respect their individual limitations. * * Mainly used by split layouts (Split Panes) where the layout is mainly defined * by the outer dimensions. * * @param beginMin {Integer} Minimum size of first widget (from size hint) * @param beginIdeal {Integer} Ideal size of first widget (maybe after dragging the splitter) * @param beginMax {Integer} Maximum size of first widget (from size hint) * @param endMin {Integer} Minimum size of second widget (from size hint) * @param endIdeal {Integer} Ideal size of second widget (maybe after dragging the splitter) * @param endMax {Integer} Maximum size of second widget (from size hint) * @return {Map} Map with the keys <code>begin</code and <code>end</code> with the * arranged dimensions. */ arrangeIdeals : function(beginMin, beginIdeal, beginMax, endMin, endIdeal, endMax) { if (beginIdeal < beginMin || endIdeal < endMin) { if (beginIdeal < beginMin && endIdeal < endMin) { // Just increase both, can not rearrange them otherwise // Result into overflowing of the overlapping content // Should normally not happen through auto sizing! beginIdeal = beginMin; endIdeal = endMin; } else if (beginIdeal < beginMin) { // Reduce end, increase begin to min endIdeal -= (beginMin - beginIdeal); beginIdeal = beginMin; // Re-check to keep min size of end if (endIdeal < endMin) { endIdeal = endMin; } } else if (endIdeal < endMin) { // Reduce begin, increase end to min beginIdeal -= (endMin - endIdeal); endIdeal = endMin; // Re-check to keep min size of begin if (beginIdeal < beginMin) { beginIdeal = beginMin; } } } if (beginIdeal > beginMax || endIdeal > endMax) { if (beginIdeal > beginMax && endIdeal > endMax) { // Just reduce both, can not rearrange them otherwise // Leaves a blank area in the pane! beginIdeal = beginMax; endIdeal = endMax; } else if (beginIdeal > beginMax) { // Increase end, reduce begin to max endIdeal += (beginIdeal - beginMax); beginIdeal = beginMax; // Re-check to keep max size of end if (endIdeal > endMax) { endIdeal = endMax; } } else if (endIdeal > endMax) { // Increase begin, reduce end to max beginIdeal += (endIdeal - endMax); endIdeal = endMax; // Re-check to keep max size of begin if (beginIdeal > beginMax) { beginIdeal = beginMax; } } } return { begin : beginIdeal, end : endIdeal }; } } });