//////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2003-2006 Adobe Macromedia Software LLC and its licensors. // All Rights Reserved. The following is Source Code and is subject to all // restrictions on such code as contained in the End User License Agreement // accompanying this product. // //////////////////////////////////////////////////////////////////////////////// package mx.managers { import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import mx.core.Application; import mx.core.UIComponent; import mx.core.mx_internal; import mx.events.FlexEvent; import mx.managers.layoutClasses.PriorityQueue; use namespace mx_internal; /** * The LayoutManager is the engine behind * Flex's measurement and layout strategy. * Layout is performed in three phases; commit, measurement, and layout. * *
Each phase is distinct from the others and all UIComponents of * one phase are processed prior to moving on to the next phase. * During the processing of UIComponents in a phase, requests for * UIComponents to get re-processed by some phase may occur. * These requests are queued and are only processed * during the next run of the phase.
* *The commit phase begins with a call to
* validateProperties()
, which walks through a list
* (sorted by nesting level) of objects calling each object's
*
* validateProperties()
method.
The objects in the list are processed by nesting order, * with the most deeply nested object accessed first. * This can also be referred to as bottom-up inside-out ordering.
* *This phase allows components whose contents depend on property
* settings to configure themselves prior to the measurement
* and the layout phases.
* For the sake of performance, sometimes a component's property setter
* method does not do all the work to update to the new property value.
* Instead, the property setter calls the invalidateProperties()
* method, deferring the work until this phase runs.
* This prevents unnecessary work if the property is set multiple times.
The measurement phase begins with a call to
* validateSize()
, which walks through a list
* (sorted by nesting level) of objects calling each object's
* validateSize()
* method to determine if the object has changed in size.
If an object's
* invalidateSize()
method was previously called,
* then the validateSize()
method is called.
* If the size or position of the object was changed as a result of the
* validateSize()
call, then the object's
*
* invalidateDisplayList()
method is called, thus adding
* the object to the processing queue for the next run of the layout phase.
* Additionally, the object's parent is marked for both measurement
* and layout phases, by calling
*
* invalidateSize()
and
*
* invalidateDisplayList()
respectively.
The objects in the list are processed by nesting order, * with the most deeply nested object accessed first. * This can also be referred to as bottom-up inside-out ordering.
* *The layout phase begins with a call to the
* validateDisplayList()
method, which walks through a list
* (reverse sorted by nesting level) of objects calling each object's
*
* validateDisplayList()
method to request the object to size
* and position all components contained within it (i.e. its children).
If an object's
* invalidateDisplayList()
method was previously called,
* then validateDisplayList()
method for the object is called.
The objects in the list are processed in reversed nesting order, * with the least deeply nested object accessed first. * This can also be referred to as top-down or outside-in ordering.
* *In general, components do not override the validateProperties()
,
* validateSize()
, or validateDisplayList()
methods.
* In the case of UIComponents, most components override the
* commitProperties()
, measure()
, or
* updateDisplayList()
methods, which are called
* by the validateProperties()
,
* validateSize()
, or
* validateDisplayList()
methods, respectively.
At application startup, a single instance of the LayoutManager is created
* and stored in the UIComponent.layoutManager
property.
* All components are expected to use that instance.
* If you do not have access to the UIComponent object,
* you can also access the LayoutManager using the static
* LayoutManager.getInstance()
method.
true
, measurement and layout are done in phases, one phase
* per screen update.
* All components have their validateProperties()
* and commitProperties()
methods
* called until all their properties are validated.
* The screen will then be updated.
*
* Then all components will have their validateSize()
* and measure()
* methods called until all components have been measured, then the screen
* will be updated again.
Finally, all components will have their
* validateDisplayList()
and
* updateDisplayList()
methods called until all components
* have been validated, and the screen will be updated again.
* If in the validation of one phase, an earlier phase gets invalidated,
* the LayoutManager starts over.
* This is more efficient when large numbers of components
* are being created an initialized. The framework is responsible for setting
* this property.
If false
, all three phases are completed before the screen is updated.
validateProperties()
method called.
* A component should call this method when a property changes.
* Typically, a property setter method
* stores a the new value in a temporary variable and calls
* the invalidateProperties()
method
* so that its validateProperties()
* and commitProperties()
methods are called
* later, when the new value will actually be applied to the component and/or
* its children. The advantage of this strategy is that often, more than one
* property is changed at a time and the properties may interact with each
* other, or repeat some code as they are applied, or need to be applied in
* a specific order. This strategy allows the most efficient method of
* applying new property values.
*
* @param obj The object whose property changed.
*/
public function invalidateProperties(obj:ILayoutManagerClient ):void
{
if (!invalidatePropertiesFlag && Application.application.systemManager)
{
invalidatePropertiesFlag = true;
if (!callLaterPending)
{
if (!callLaterObject)
{
callLaterObject = new UIComponent();
callLaterObject.systemManager =
Application.application.systemManager;
callLaterObject.callLater(waitAFrame);
}
else
{
callLaterObject.callLater(doPhasedInstantiation);
}
callLaterPending = true;
}
}
// trace("LayoutManager adding " + Object(obj) + " to invalidatePropertiesQueue");
if (targetLevel <= obj.nestLevel)
invalidateClientPropertiesFlag = true;
invalidatePropertiesQueue.addObject(obj, obj.nestLevel);
// trace("LayoutManager added " + Object(obj) + " to invalidatePropertiesQueue");
}
/**
* Adds an object to the list of components that want their
* validateSize()
method called.
* Called when an object's size changes.
*
* An object's size can change for two reasons:
* *label
is changed.minWidth
, minHeight
,
* explicitWidth
, explicitHeight
,
* maxWidth
, or maxHeight
.When the first condition occurs, it's necessary to recalculate * the measurements for the object. * When the second occurs, it's not necessary to recalculate the * measurements because the new size of the object is known. * However, it's necessary to remeasure and relayout the object's * parent.
* * @param obj The object whose size changed. */ public function invalidateSize(obj:ILayoutManagerClient ):void { if (!invalidateSizeFlag && Application.application.systemManager) { invalidateSizeFlag = true; if (!callLaterPending) { if (!callLaterObject) { callLaterObject = new UIComponent(); callLaterObject.systemManager = Application.application.systemManager; callLaterObject.callLater(waitAFrame); } else { callLaterObject.callLater(doPhasedInstantiation); } callLaterPending = true; } } // trace("LayoutManager adding " + Object(obj) + " to invalidateSizeQueue"); if (targetLevel <= obj.nestLevel) invalidateClientSizeFlag = true; invalidateSizeQueue.addObject(obj, obj.nestLevel); // trace("LayoutManager added " + Object(obj) + " to invalidateSizeQueue"); } /** * Called when a component changes in some way that its layout and/or visuals * need to be changed. * In that case, it is necessary to run the component's layout algorithm, * even if the component's size hasn't changed. For example, when a new child component * is added, or a style property changes or the component has been given * a new size by its parent. * * @param obj The object that changed. */ public function invalidateDisplayList(obj:ILayoutManagerClient ):void { if (!invalidateDisplayListFlag && Application.application.systemManager) { invalidateDisplayListFlag = true; if (!callLaterPending) { if (!callLaterObject) { callLaterObject = new UIComponent(); callLaterObject.systemManager = Application.application.systemManager; callLaterObject.callLater(waitAFrame); } else { callLaterObject.callLater(doPhasedInstantiation); } callLaterPending = true; } } // trace("LayoutManager adding " + Object(obj) + " to invalidateDisplayListQueue"); invalidateDisplayListQueue.addObject(obj, obj.nestLevel); // trace("LayoutManager added " + Object(obj) + " to invalidateDisplayListQueue"); } //-------------------------------------------------------------------------- // // Methods: Commitment, measurement, layout, and drawing // //-------------------------------------------------------------------------- /** * Validates all components whose properties have changed and have called * theinvalidateProperties()
method.
* It calls the validateProperties()
method on those components
* and will call validateProperties()
on any other components that are
* invalidated while validating other components.
*/
private function validateProperties():void
{
// trace("--- LayoutManager: validateProperties --->");
// Keep traversing the invalidatePropertiesQueue until we've reached the end.
// More elements may get added to the queue while we're in this loop, or a
// a recursive call to this function may remove elements from the queue while
// we're in this loop.
var obj:ILayoutManagerClient = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallest());
while (obj)
{
// trace("LayoutManager calling validateProperties() on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
obj.validateProperties();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// Once we start, don't stop.
obj = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallest());
}
if (invalidatePropertiesQueue.isEmpty())
{
// trace("Properties Queue is empty");
invalidatePropertiesFlag = false;
}
// trace("<--- LayoutManager: validateProperties ---");
}
/**
* Validates all components whose properties have changed and have called
* the invalidateSize()
method.
* It calls the validateSize()
method on those components
* and will call the validateSize()
method
* on any other components that are
* invalidated while validating other components.
* The validateSize() method starts with
* the most deeply nested child in the tree of display objects
*/
private function validateSize():void
{
// trace("--- LayoutManager: validateSize --->");
var obj:ILayoutManagerClient = ILayoutManagerClient(invalidateSizeQueue.removeLargest());
while (obj)
{
// trace("LayoutManager calling validateSize() on " + Object(obj));
obj.validateSize();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// trace("LayoutManager validateSize: " + Object(obj) + " " + IFlexDisplayObject(obj).measuredWidth + " " + IFlexDisplayObject(obj).measuredHeight);
obj = ILayoutManagerClient(invalidateSizeQueue.removeLargest());
}
if (invalidateSizeQueue.isEmpty())
{
// trace("Measurement Queue is empty");
invalidateSizeFlag = false;
}
// trace("<--- LayoutManager: validateSize ---");
}
/**
* Validates all components whose properties have changed and have called
* the invalidateDisplayList()
method.
* It calls validateDisplayList()
method on those components
* and will call the validateDisplayList()
method
* on any other components that are
* invalidated while validating other components.
* The validateDisplayList()
method starts with
* the least deeply nested child in the tree of display objects
*
*/
private function validateDisplayList():void
{
// trace("--- LayoutManager: validateDisplayList --->");
var obj:ILayoutManagerClient = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
while (obj)
{
// trace("LayoutManager calling validateDisplayList on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
obj.validateDisplayList();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// trace("LayoutManager return from validateDisplayList on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
// Once we start, don't stop.
obj = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
}
if (invalidateDisplayListQueue.isEmpty())
{
// trace("Layout Queue is empty");
invalidateDisplayListFlag = false;
}
// trace("<--- LayoutManager: validateDisplayList ---");
}
/**
* @private
*/
private function doPhasedInstantiation():void
{
// trace(">>DoPhasedInstantation");
// If phasing, do only one phase: validateProperties(),
// validateSize(), or validateDisplayList().
if (usePhasedInstantiation)
{
if (invalidatePropertiesFlag)
{
validateProperties();
// The Preloader listens for this event.
Application.application.dispatchEvent(
new Event("validatePropertiesComplete"));
}
else if (invalidateSizeFlag)
{
validateSize();
// The Preloader listens for this event.
Application.application.dispatchEvent(
new Event("validateSizeComplete"));
}
else if (invalidateDisplayListFlag)
{
validateDisplayList();
// The Preloader listens for this event.
Application.application.dispatchEvent(
new Event("validateDisplayListComplete"));
}
}
// Otherwise, do one pass of all three phases.
else
{
if (invalidatePropertiesFlag)
validateProperties();
if (invalidateSizeFlag)
validateSize();
if (invalidateDisplayListFlag)
validateDisplayList();
}
//// trace("invalidatePropertiesFlag " + invalidatePropertiesFlag);
//// trace("invalidateSizeFlag " + invalidateSizeFlag);
//// trace("invalidateDisplayListFlag " + invalidateDisplayListFlag);
if (invalidatePropertiesFlag ||
invalidateSizeFlag ||
invalidateDisplayListFlag)
{
callLaterObject.callLater(doPhasedInstantiation);
}
else
{
usePhasedInstantiation = false;
callLaterPending = false;
var obj:ILayoutManagerClient = ILayoutManagerClient(updateCompleteQueue.removeLargest());
while (obj)
{
if (!obj.initialized && obj.processedDescriptors)
obj.initialized = true;
obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
obj.updateCompletePendingFlag = false;
obj = ILayoutManagerClient(updateCompleteQueue.removeLargest());
}
// trace("updateComplete");
dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
}
// trace("<To guarantee that the values are updated,
* you can call the validateClient()
method.
* It updates all properties in all components whose nest level is greater than or equal
* to the target component before returning.
* Call this method only when necessary as it is a computationally intensive call.
validateProperties()
, commitProperties()
,
* validateSize()
, measure()
,
* validateDisplayList()
,
* and updateDisplayList()
methods called.
*
* @param skipDisplayList If true
,
* does not call the validateDisplayList()
* and updateDisplayList()
methods.
*/
public function validateClient(target:ILayoutManagerClient , skipDisplayList:Boolean = false):void
{
var obj:ILayoutManagerClient;
var i:int = 0;
var done:Boolean = false;
var oldTargetLevel:int = targetLevel;
// the theory here is that most things that get validated are deep in the tree
// and so there won't be nested calls to validateClient. However if there is,
// we don't want to have a more sophisticated scheme of keeping track
// of dirty flags at each level that is being validated, but we definitely
// do not want to keep scanning the queues unless we're pretty sure that
// something might be dirty so we just say that if something got dirty
// during this call at a deeper nesting than the first call to validateClient
// then we'll scan the queues. So we only change targetLevel if we're the
// outer call to validateClient and only that call restores it.
if (targetLevel == int.MAX_VALUE)
targetLevel = target.nestLevel;
// trace("--- LayoutManager: validateClient ---> target = " + target);
while (!done)
{
// assume we won't find anything
done = true;
// Keep traversing the invalidatePropertiesQueue until we've reached the end.
// More elements may get added to the queue while we're in this loop, or a
// a recursive call to this function may remove elements from the queue while
// we're in this loop.
obj = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallestChild(target));
while (obj)
{
// trace("LayoutManager calling validateProperties() on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
obj.validateProperties();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// Once we start, don't stop.
obj = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallestChild(target));
}
if (invalidatePropertiesQueue.isEmpty())
{
// trace("Properties Queue is empty");
invalidatePropertiesFlag = false;
invalidateClientPropertiesFlag = false;
}
// trace("--- LayoutManager: validateSize --->");
obj = ILayoutManagerClient(invalidateSizeQueue.removeLargestChild(target));
while (obj)
{
// trace("LayoutManager calling validateSize() on " + Object(obj));
obj.validateSize();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// trace("LayoutManager validateSize: " + Object(obj) + " " + IFlexDisplayObject(obj).measuredWidth + " " + IFlexDisplayObject(obj).measuredHeight);
if (invalidateClientPropertiesFlag)
{
// did any properties get invalidated while validating size?
obj = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallestChild(target));
if (obj)
{
// re-queue it. we'll pull it at the beginning of the loop
invalidatePropertiesQueue.addObject(obj, obj.nestLevel);
done = false;
break;
}
}
obj = ILayoutManagerClient(invalidateSizeQueue.removeLargestChild(target));
}
if (invalidateSizeQueue.isEmpty())
{
// trace("Measurement Queue is empty");
invalidateSizeFlag = false;
invalidateClientSizeFlag = false;
}
if (!skipDisplayList)
{
// trace("--- LayoutManager: validateDisplayList --->");
obj = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallestChild(target));
while (obj)
{
// trace("LayoutManager calling validateDisplayList on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
obj.validateDisplayList();
if (!obj.updateCompletePendingFlag)
updateCompleteQueue.addObject(obj, obj.nestLevel);
// trace("LayoutManager return from validateDisplayList on " + Object(obj) + " " + DisplayObject(obj).width + " " + DisplayObject(obj).height);
if (invalidateClientPropertiesFlag)
{
// did any properties get invalidated while validating size?
obj = ILayoutManagerClient(invalidatePropertiesQueue.removeSmallestChild(target));
if (obj)
{
// re-queue it. we'll pull it at the beginning of the loop
invalidatePropertiesQueue.addObject(obj, obj.nestLevel);
done = false;
break;
}
}
if (invalidateClientSizeFlag)
{
obj = ILayoutManagerClient(invalidateSizeQueue.removeLargestChild(target));
if (obj)
{
// re-queue it. we'll pull it at the beginning of the loop
invalidateSizeQueue.addObject(obj, obj.nestLevel);
done = false;
break;
}
}
// Once we start, don't stop.
obj = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallestChild(target));
}
if (invalidateDisplayListQueue.isEmpty())
{
// trace("Layout Queue is empty");
invalidateDisplayListFlag = false;
}
}
}
if (oldTargetLevel == int.MAX_VALUE)
{
targetLevel = int.MAX_VALUE;
if (!skipDisplayList)
{
obj = ILayoutManagerClient(updateCompleteQueue.removeLargestChild(target));
while (obj)
{
if (!obj.initialized)
obj.initialized = true;
obj.dispatchEvent(new FlexEvent(FlexEvent.UPDATE_COMPLETE));
obj.updateCompletePendingFlag = false;
obj = ILayoutManagerClient(updateCompleteQueue.removeLargestChild(target));
}
}
}
// trace("<--- LayoutManager: validateClient --- target = " + target);
}
/**
* Returns true
if there are components that need validating;
* false
if all components have been validated.
*/
public function isInvalid():Boolean
{
return invalidatePropertiesFlag ||
invalidateSizeFlag ||
invalidateDisplayListFlag;
}
/**
* @private
* callLater() is called immediately after an object is created.
* We really want to wait one more frame before starting in.
*/
private function waitAFrame():void
{
//// trace(">>LayoutManager:WaitAFrame");
callLaterObject.callLater(doPhasedInstantiation);
//// trace("<