@qooxdoo/framework
Version:
The JS Framework for Coders
396 lines (336 loc) • 11.7 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2008 Dihedrals.com, http://www.dihedrals.com
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Chris Banford (zermattchris)
* Fabian Jakobs (fjakobs)
************************************************************************ */
/**
* A basic layout, which supports positioning of child widgets in a 'flowing'
* manner, starting at the container's top/left position, placing children left to right
* (like a HBox) until the there's no remaining room for the next child. When
* out of room on the current line of elements, a new line is started, cleared
* below the tallest child of the preceding line -- a bit like using 'float'
* in CSS, except that a new line wraps all the way back to the left.
*
* *Features*
*
* <ul>
* <li> Reversing children order </li>
* <li> Manual line breaks </li>
* <li> Horizontal alignment of lines </li>
* <li> Vertical alignment of individual widgets within a line </li>
* <li> Margins with horizontal margin collapsing </li>
* <li> Horizontal and vertical spacing </li>
* <li> Height for width calculations </li>
* <li> Auto-sizing </li>
* </ul>
*
* *Item Properties*
*
* <ul>
* <li><strong>lineBreak</strong> <em>(Boolean)</em>: If set to <code>true</code>
* a forced line break will happen after this child widget.
* </li>
* <li><strong>stretch</strong> <em>(Boolean)</em>: If set to <code>true</code>
* the widget will be stretched to the remaining line width. This requires
* lineBreak to be true.
* </li>
* </ul>
*
* *Example*
*
* Here is a little example of how to use the Flow layout.
*
* <pre class="javascript">
* var flowlayout = new qx.ui.layout.Flow();
*
* flowlayout.setAlignX( "center" ); // Align children to the X axis of the container (left|center|right)
*
* var container = new qx.ui.container.Composite(flowlayout);
* this.getRoot().add(container, {edge: 0});
*
* var button1 = new qx.ui.form.Button("1. First Button", "flowlayout/test.png");
* container.add(button1);
*
* var button2 = new qx.ui.form.Button("2. Second longer Button...", "flowlayout/test.png");
* // Have this child create a break in the current Line (next child will always start a new Line)
* container.add(button2, {lineBreak: true});
*
* var button3 = new qx.ui.form.Button("3rd really, really, really long Button", "flowlayout/test.png");
* button3.setHeight(100); // tall button
* container.add(button3);
*
* var button4 = new qx.ui.form.Button("Number 4", "flowlayout/test.png");
* button4.setAlignY("bottom");
* container.add(button4);
*
* var button5 = new qx.ui.form.Button("20px Margins around the great big 5th button!");
* button5.setHeight(100); // tall button
* button5.setMargin(20);
* container.add(button5, {lineBreak: true}); // Line break after this button.
*
* var button6 = new qx.ui.form.Button("Number 6", "flowlayout/test.png");
* button6.setAlignY("middle"); // Align this child to the vertical center of this line.
* container.add(button6);
*
* var button7 = new qx.ui.form.Button("7th a wide, short button", "flowlayout/test.png");
* button7.setMaxHeight(20); // short button
* container.add(button7);
* </pre>
*
* *External Documentation*
*
* <a href='http://manual.qooxdoo.org/${qxversion}/pages/layout/flow.html'>
* Extended documentation</a> and links to demos of this layout in the qooxdoo manual.
*/
qx.Class.define("qx.ui.layout.Flow",
{
extend : qx.ui.layout.Abstract,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param spacingX {Integer?0} The spacing between child widgets {@link #spacingX}.
* @param spacingY {Integer?0} The spacing between the lines {@link #spacingY}.
* @param alignX {String?"left"} Horizontal alignment of the whole children
* block {@link #alignX}.
*/
construct : function(spacingX, spacingY, alignX)
{
this.base(arguments);
if (spacingX) {
this.setSpacingX(spacingX);
}
if (spacingY) {
this.setSpacingY(spacingY);
}
if (alignX) {
this.setAlignX(alignX);
}
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/**
* Horizontal alignment of the whole children block. The horizontal
* alignment of the child is completely ignored in HBoxes (
* {@link qx.ui.core.LayoutItem#alignX}).
*/
alignX :
{
check : [ "left", "center", "right" ],
init : "left",
apply : "_applyLayoutChange"
},
/**
* Vertical alignment of each child. Can be overridden through
* {@link qx.ui.core.LayoutItem#alignY}.
*/
alignY :
{
check : [ "top", "middle", "bottom"],
init : "top",
apply : "_applyLayoutChange"
},
/** Horizontal spacing between two children */
spacingX :
{
check : "Integer",
init : 0,
apply : "_applyLayoutChange"
},
/**
* The vertical spacing between the lines.
*/
spacingY :
{
check : "Integer",
init : 0,
apply : "_applyLayoutChange"
},
/** Whether the actual children list should be laid out in reversed order. */
reversed :
{
check : "Boolean",
init : false,
apply : "_applyLayoutChange"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
/*
---------------------------------------------------------------------------
LAYOUT INTERFACE
---------------------------------------------------------------------------
*/
// overridden
verifyLayoutProperty : qx.core.Environment.select("qx.debug",
{
"true" : function(item, name, value) {
var validProperties = ["lineBreak", "stretch"];
this.assertInArray(name, validProperties, "The property '"+name+"' is not supported by the flow layout!" );
},
"false" : null
}),
// overridden
connectToWidget : function(widget)
{
this.base(arguments, widget);
// Necessary to be able to calculate the lines for the flow layout.
// Otherwise the layout calculates the needed width and height by using
// only one line of items which is leading to the wrong height. This
// wrong height does e.g. suppress scrolling since the scroll pane does
// not know about the correct needed height.
if (widget) {
widget.setAllowShrinkY(false);
}
},
/**
* The FlowLayout tries to add as many Children as possible to the current 'Line'
* and when it sees that the next Child won't fit, it starts on a new Line, continuing
* until all the Children have been added.
* To enable alignX "left", "center", "right" renderLayout has to calculate the positions
* of all a Line's children before it draws them.
*
* @param availWidth {Integer} Final width available for the content (in pixel)
* @param availHeight {Integer} Final height available for the content (in pixel)
* @param padding {Map} Map containing the padding values. Keys:
* <code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>
*/
renderLayout : function(availWidth, availHeight, padding)
{
var children = this._getLayoutChildren();
if (this.getReversed()) {
children = children.concat().reverse();
}
var lineCalculator = new qx.ui.layout.LineSizeIterator(
children,
this.getSpacingX()
);
var lineTop = padding.top;
while (lineCalculator.hasMoreLines())
{
var line = lineCalculator.computeNextLine(availWidth);
this.__renderLine(line, lineTop, availWidth, padding);
lineTop += line.height + this.getSpacingY();
}
},
/**
* Render a line in the flow layout
*
* @param line {Map} A line configuration as returned by
* {@link LineSizeIterator#computeNextLine}.
* @param lineTop {Integer} The line's top position
* @param availWidth {Integer} The available line width
* @param padding {Map} Map containing the padding values. Keys:
* <code>top</code>, <code>bottom</code>, <code>left</code>, <code>right</code>
*/
__renderLine : function(line, lineTop, availWidth, padding)
{
var util = qx.ui.layout.Util;
var left = padding.left;
if (this.getAlignX() != "left") {
left = padding.left + availWidth - line.width;
if (this.getAlignX() == "center") {
left = padding.left + Math.round((availWidth - line.width) / 2);
}
}
for (var i=0; i<line.children.length; i++)
{
var child = line.children[i];
var size = child.getSizeHint();
var marginTop = child.getMarginTop();
var marginBottom = child.getMarginBottom();
var top = util.computeVerticalAlignOffset(
child.getAlignY() || this.getAlignY(),
marginTop + size.height + marginBottom,
line.height,
marginTop, marginBottom
);
var layoutProps = child.getLayoutProperties();
if (layoutProps.stretch && layoutProps.stretch) {
size.width += availWidth - line.width;
}
child.renderLayout(
left + line.gapsBefore[i],
lineTop + top,
size.width,
size.height
);
left += line.gapsBefore[i] + size.width;
}
},
// overridden
_computeSizeHint : function() {
return this.__computeSize(Infinity);
},
// overridden
hasHeightForWidth : function() {
return true;
},
// overridden
getHeightForWidth : function(width) {
return this.__computeSize(width).height;
},
/**
* Returns the list of children fitting in the last row of the given width.
* @param width {Number} The width to use for the calculation.
* @return {Array} List of children in the first row.
*/
getLastLineChildren : function(width) {
var lineCalculator = new qx.ui.layout.LineSizeIterator(
this._getLayoutChildren(),
this.getSpacingX()
);
var lineData = [];
while (lineCalculator.hasMoreLines()) {
lineData = lineCalculator.computeNextLine(width).children;
}
return lineData;
},
/**
* Compute the preferred size optionally constrained by the available width
*
* @param availWidth {Integer} The available width
* @return {Map} Map containing the preferred height and width of the layout
*/
__computeSize : function(availWidth)
{
var lineCalculator = new qx.ui.layout.LineSizeIterator(
this._getLayoutChildren(),
this.getSpacingX()
);
var height = 0;
var width = 0;
var lineCount = 0;
while (lineCalculator.hasMoreLines())
{
var line = lineCalculator.computeNextLine(availWidth);
lineCount += 1;
width = Math.max(width, line.width);
height += line.height;
}
return {
width : width,
height : height + this.getSpacingY() * (lineCount-1)
};
}
}
});