@qooxdoo/framework
Version:
The JS Framework for Coders
281 lines (234 loc) • 7.66 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 layout queue manages all widgets, which need a recalculation of their
* layout. The {@link #flush} method computes the layout of all queued widgets
* and their dependent widgets.
*/
qx.Class.define("qx.ui.core.queue.Layout",
{
statics :
{
/** @type {Map} This contains all the queued widgets for the next flush. */
__queue : {},
/** Nesting level cache **/
__nesting : {},
/**
* Clears the widget from the internal queue. Normally only used
* during interims disposes of one or a few widgets.
*
* @param widget {qx.ui.core.Widget} The widget to clear
*/
remove : function(widget) {
delete this.__queue[widget.$$hash];
},
/**
* Mark a widget's layout as invalid and add its layout root to
* the queue.
*
* Should only be used by {@link qx.ui.core.Widget}.
*
* @param widget {qx.ui.core.Widget} Widget to add.
*/
add : function(widget)
{
this.__queue[widget.$$hash] = widget;
qx.ui.core.queue.Manager.scheduleFlush("layout");
},
/**
* Check whether the queue has scheduled changes for a widget.
* Note that the layout parent can have changes scheduled that
* affect the children widgets.
*
* @param widget {qx.ui.core.Widget} Widget to check.
* @return {Boolean} Whether the widget given has layout changes queued.
*/
isScheduled : function(widget) {
return !!this.__queue[widget.$$hash];
},
/**
* Update the layout of all widgets, which layout is marked as invalid.
*
* This is used exclusively by the {@link qx.ui.core.queue.Manager}.
*
*/
flush : function()
{
// get sorted widgets to (re-)layout
var queue = this.__getSortedQueue();
// iterate in reversed order to process widgets with the smallest nesting
// level first because these may affect the inner lying children
for (var i=queue.length-1; i>=0; i--)
{
var widget = queue[i];
// continue if a relayout of one of the root's parents has made the
// layout valid
if (widget.hasValidLayout()) {
continue;
}
// overflow areas or qx.ui.root.*
if (widget.isRootWidget() && !widget.hasUserBounds())
{
// This is a real root widget. Set its size to its preferred size.
var hint = widget.getSizeHint();
widget.renderLayout(0, 0, hint.width, hint.height);
}
else
{
// This is an inner item of layout changes. Do a relayout of its
// children without changing its position and size.
var bounds = widget.getBounds();
widget.renderLayout(bounds.left, bounds.top, bounds.width, bounds.height);
}
}
},
/**
* Get the widget's nesting level. Top level widgets have a nesting level
* of <code>0</code>.
*
* @param widget {qx.ui.core.Widget} The widget to query.
* @return {Integer} The nesting level
*/
getNestingLevel : function(widget)
{
var cache = this.__nesting;
var level = 0;
var parent = widget;
// Detecting level
while (true)
{
if (cache[parent.$$hash] != null)
{
level += cache[parent.$$hash];
break;
}
if (!parent.$$parent) {
break;
}
parent = parent.$$parent;
level += 1;
}
// Update the processed hierarchy (runs from inner to outer)
var leveldown = level;
while (widget && widget !== parent)
{
cache[widget.$$hash] = leveldown--;
widget = widget.$$parent;
}
return level;
},
/**
* Group widget by their nesting level.
*
* @return {Map[]} A sparse array. Each entry of the array contains a widget
* map with all widgets of the same level as the array index.
*/
__getLevelGroupedWidgets : function()
{
var VisibilityQueue = qx.ui.core.queue.Visibility;
// clear cache
this.__nesting = {};
// sparse level array
var levels = [];
var queue = this.__queue;
var widget, level;
for (var hash in queue)
{
widget = queue[hash];
if (VisibilityQueue.isVisible(widget))
{
level = this.getNestingLevel(widget);
// create hierarchy
if (!levels[level]) {
levels[level] = {};
}
// store widget in level map
levels[level][hash] = widget;
// remove widget from layout queue
delete queue[hash];
}
}
return levels;
},
/**
* Compute all layout roots of the given widgets. Layout roots are either
* root widgets or widgets, which preferred size has not changed by the
* layout changes of its children.
*
* This function returns the roots ordered by their nesting factors. The
* layout with the largest nesting level comes first.
*
* @return {qx.ui.core.Widget[]} Ordered list or layout roots.
*/
__getSortedQueue : function()
{
var sortedQueue = [];
var levels = this.__getLevelGroupedWidgets();
for (var level=levels.length-1; level>=0; level--)
{
// Ignore empty levels (levels is an sparse array)
if (!levels[level]) {
continue;
}
for (var hash in levels[level])
{
var widget = levels[level][hash];
// This is a real layout root. Add it directly to the list
if (level == 0 || widget.isRootWidget() || widget.hasUserBounds())
{
sortedQueue.push(widget);
widget.invalidateLayoutCache();
continue;
}
// compare old size hint to new size hint
var oldSizeHint = widget.getSizeHint(false);
if (oldSizeHint)
{
widget.invalidateLayoutCache();
var newSizeHint = widget.getSizeHint();
var hintChanged = (
!widget.getBounds() ||
oldSizeHint.minWidth !== newSizeHint.minWidth ||
oldSizeHint.width !== newSizeHint.width ||
oldSizeHint.maxWidth !== newSizeHint.maxWidth ||
oldSizeHint.minHeight !== newSizeHint.minHeight ||
oldSizeHint.height !== newSizeHint.height ||
oldSizeHint.maxHeight !== newSizeHint.maxHeight
);
}
else
{
hintChanged = true;
}
if (hintChanged)
{
// Since the level is > 0, the widget must
// have a parent != null.
var parent = widget.getLayoutParent();
if (!levels[level-1]) {
levels[level-1] = {};
}
levels[level-1][parent.$$hash] = parent;
}
else
{
// this is an internal layout root since its own preferred size
// has not changed.
sortedQueue.push(widget);
}
}
}
return sortedQueue;
}
}
});