With the heavy skinning-oriented Gumbo, we are in need of light-weight (memory-wise) graphics primitives. Since every single display object brings a lot of overhead, the primary way of acheiving this is by minimizing the number of display objects. In Gumbo we're introducing the Group and GraphicElment relationship:
The goal of this spec is to formalize the relationship between GraphicElements and Group with interface contracts, so that anyone can implement a custom graphic element, without the need to extend the Gumbo GraphicElement base class.
Enable implementation of lighter-weight VisualElements that:
The rendering order of the GraphicElements, as with any child of a GroupBase container, is well defined by GroupBase. By default the rendering order is the same as the child order, and explicit layer property can be set to allow for a rendering order, different from the child order. The important part is that there's a particular rendering order that is well defined.
Lets take a look at any three graphic elements A, B, C, such that renderingOrder(A) < renderingOrder(B) < renderingOrder(C). We can see that if A and C are drawing to the same DisplayObject, then: Either B must also draw to the same DisplayObject Or B's drawing must not overlap with A's and C's drawing.
The second clause "B's drawing must not overlap with A's and C's drawing" is hard/expensive to evaluate. Therefore Group will take the conservative approach and always assume that all graphic element drawings always overlap. Thus for a graphic element A and C to share a DisplayObject, B must also share the same DisplayObject.
This leads to the Group's algorithm of creating/assigning display objects to graphic elements. The Group will go through a rendering-order sorted list of elements and always keep track of the current sequence of elements with shared display object. If the current element can share the display object, then extend the current sequence, otherwise start a new sequence with that element. The Group ensures that the display objects are sorted in the correct rendering order defined by the elements.
Diagram 1:
Diagram 2:
Diagram 3:
Diagram 4:
Diagram 5:
Diagram 6:
package spark.core
{
/**
* ISharedDisplayObject defines the minimum requirements for a
* <code>DisplayObject</code> to be shared between <code>IGraphicElement</code>
* objects.
*
* <code>Group</code> uses <code>ISharedDisplayObject</code> to manage
* invalidation and redrawing of sequences of <code>IGraphicElement</code>
* objects that share a <code>DisplayObject</code>.
*
* Typically when implementing a custom <code>IGraphicElement</code>
* Developers also implement this interface for the <code>DisplayObject</code>
* that the custom <code>IGraphicElement</code> creates.
*/
public interface ISharedDisplayObject
{
/**
* True when any of the <code>IGraphicElement</code> objects, that share
* this <code>DisplayObject</code>, needs to redraw. This is used internally
* by the <code>Group</code> class and developers don't typically use this.
* The <code>Group</code> sets and reads back this property in order to
* determine which graphic elements to validate.
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
function get redrawRequested():Boolean;
function set redrawRequested(value:Boolean):void;
}
}
package spark.core
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import spark.components.Group;
import mx.core.IVisualElement;
/**
* The <code>IGraphicElement</code> is implemented by IVisualElements that
* take advantage of the parent <code>Group's</code> <code>DisplayObject</code>
* management.
*
* <p>One typical use case is <code>DisplayObject</code> sharing.
* <code>Group</code> organizes its
* <code>IGraphicElement</code> children in sequences that share and draw to
* the same <code>DisplayObject</code>.
* The <code>DisplayObject</code> is created by the first element in the
* sequence.</p>
*
* <p>Another use case is when an element does not derrive from
* <code>DisplayObject</code> but instead maintains, creates and/or destroys
* its own <code>DisplayObject</code>. The <code>Group</code> will ensure to
* call the element to create the <code>DisplayObject</code>, add the
* <code>DisplayObject</code> as its child at the correct index as well as
* handle its removal.</p>
*
* Typically a Developer will extend the <code>GraphicElement</code> class
* instead of directly implementing the <code>IGraphciElement</code>
* interface as <code>GraphicElement</code> already provides most of the
* required functionality.
*
*/
public interface IGraphicElement extends IVisualElement
{
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// displayObject
//----------------------------------
/**
* The shared <code>DisplayObject</code> where this
* <code>IGraphicElement</code> is drawn.
*
* Implementers should not create the <code>DisplayObject</code>
* here, but in <code>createDisplayObject()</code>.
*
* @see #createDisplayObject
* @see #validateDisplayList
* @see #sharedIndex
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
function get displayObject():DisplayObject;
//----------------------------------
// sharedIndex
//----------------------------------
/**
* The index of this <code>IGraphicElement</code> in the sequence
* of elements that share the same <code>DisplayObject</code>.
*
* A value of -1 indicates that this element doesn't
* share its <code>displayObject</code> with other elements and its sequence
* doesn't contain any other elements.
*
* A value of 0 or greater indicates the position of this element in its
* sequence.
*
* A value of 0 also indicates that this element creates the
* <code>DisplayObject</code> for its sequence.
*
* <code>Group</code> manages this property.
*
* @see #displayObject
* @see #createDisplayObject
*
* @langversion 3.0
* @playerversion Flash 10
* @playerversion AIR 1.5
* @productversion Flex 4
*/
function get sharedIndex():int;
/**
* @private
*/
function set sharedIndex(value:int):void;
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Creates a new <code>DisplayObject</code> where this <code>IGraphicElement</code>
* is drawn.
*
* Subsequent calls to the getter of the <code>displayObject</code> property must
* return the same display object.
*
* After the <code>DisplayObject</code> is created, the parent <code>Group</code>
* will pass along the display objects to the rest of the elements in the sequence.
*
* <code>Group</code> will ensure that this method is called only when needed.
*
* <p>If the element wants to participate in the <code>DisplayObject</code>
* sharing, then the new DisplayObject must implement <code>IShareableDisplayObject</code>.
* This interface is being used by the Group to manage invalidation and
* redrawing of the graphic element sequence and typically is not directly
* used by the Developer.</p>
*
* To reevaluate the shared sequences, call the parent <code>Group</code>
* <code>graphicElementLayerChanged()</code> method.
*
* To force the <code>Group</code> to remove the element's current
* <code>DisplayObject</code> from its display list and recalculate the
* display object sharing, call the parent <code>Group</code>
* <code>discardDisplayObject()</code> method.
*
* @return The display object created
* @see #displayObject
* @see spark.components.Group#graphicElementLayerChanged
* @see spark.components.Group#discardDisplayObject
*
*/
function createDisplayObject():DisplayObject;
/**
* Determines whether this element can draw itself to the
* <code>sharedDisplayObject</code> of the sequence.
*
* <p>Typically implementers will return <code>true</code> when this
* <code>IGraphicElement</code> can cumulatively draw in the shared
* <code>DisplayObject</code> <code>graphics</code> property.
* In all cases where this <code>IGraphicElement</code> needs to set
* properties on the <code>DisplayObject</code> that don't apply to the
* rest of the elements in the sequence this method must return <code>false</code>.
* Examples for such properties are rotation, scale, transform,
* mask, alpha, filters, color transform, 3D, layer, etc.</p>
*
* When this method returns true, subsequent calls to the getter of the
* <code>displayObject</code> property must return the same display object.
*
* <p>Note that in certain cases the <code>sharedDisplayObject</code> may be
* the parent <code>Group</code> itself. In the rest of the cases the
* <code>DisplayObject</code> is created by the first element in the sequence.</p>
*
* <p>When this <code>IGraphicElement</code> needs to rebuild its sequence,
* it notifies the parent <code>Group</code> by calling its
* <code>graphicElementLayerChanged()</code> method.</p>
*
* @return Returns true when this <code>IGraphicElement</code> can draw itself
* to the shared <code>DisplayObject</code> of the sequence.
*
* @see #canShareWithPrevious
* @see #canShareWithNext
* @see spark.components.Group#graphicElementLayerChanged
*
*/
function setSharedDisplayObject(sharedDisplayObject:DisplayObject):Boolean;
/**
* Return true if this <code>IGraphicElement</code> is compatible and can
* share display objects with the previous <code>IGraphicElement</code>
* in the sequence.
*
* <p>Note that in certain cases the element may be passed offered the parent
* <code>Group</code> itself in a call to <code>setSharedDisplayObject</code>.
* In those cases, this method won't be called.</p>
*
* @param element The element that comes before this element in the sequence.
* @return Returns true when this element is compatible with the previous
* element in the sequence.
*
* @see #canShareWithNext
* @see #setSharedDisplayObject
*
*/
function canShareWithPrevious(element:IGraphicElement):Boolean;
/**
* Return true if this <code>IGraphicElement</code> is compatible and can
* share display objects with the next <code>IGraphicElement</code>
* in the sequence.
*
* @param element The element that comes after this element in the sequence.
* @return Returns true when this element is compatible with the previous
* element in the sequence.
*
* @see #canShareWithPrevious
* @see #setSharedDisplayObject
*
*/
function canShareWithNext(element:IGraphicElement):Boolean;
/**
* Called by <code>Group</code> when an <code>IGraphicElement</code>
* is added to or removed from a <code>Group</code>.
* Developers typically never need to call this method.
*
* @param parent The parent group of this <code>IGraphicElement</code>.
*
*/
function parentChanged(parent:Group):void;
/**
* Called by the parent <code>Group</code> to validate the properties of
* this element.
*
* To ensure this method is called, notify the parent <code>Group</code>
* by calling its <code>graphicElementPropertiesChanged()</code> method.
*
* Note that this method may be called even if this element have not
* notified the parent <code>Group</code>.
*
* @see #validateSize
* @see #validateDisplayList
*
*/
function validateProperties():void;
/**
* Called by the parent <code>Group</code> to validate the size of
* this element.
*
* When the size of the element changes and is going to affect the
* parent <code>Group</code> layout, the implementer is responsible
* for invalidating the parent's size and display list.
*
* To ensure this method is called, notify the parent <code>Group</code>
* by calling its <code>graphicElementSizeChanged()</code> method.
*
* Note that this method may be called even if this element have not
* notified the parent <code>Group</code>.
*
* @see #validateProperties
* @see #validateDisplayList
*
*/
function validateSize():void;
/**
* Called by the parent <code>Group</code> to redraw this element
* in its <code>displayObject</code> property.
*
* <p>If the element is the first in the sequence (<code>sharedIndex</code>
* is less than or equal to zero) it must clear the <code>displayObject</code>
* graphics and set it up as necessary for drawing the rest of the elements.</p>
*
* <p>The element must alway redraw even if it itself has not changed
* since the last time <code>validateDisplayList()</code> was called
* as the parent <code>Group</code> will redraw the whole sequence
* if any of its elements need to be redrawn.</p>
*
* <p>To ensure this method is called, notify the parent <code>Group</code>
* by calling its <code>graphicElementSizeChanged()</code> method.</p>
*
* <p>Note that this method may be called even if this element have not
* notified the parent <code>Group</code>.</p>
*
* @see #displayObject
* @see #validateProperties
* @see #validateSize
*
*/
function validateDisplayList():void;
}
}
public class Group
{
...........
/**
* Notify the host that an element has changed and needs to be redrawn.
*
* @param e The element that has changed.
*/
public function graphicElementChanged(e:IGraphicElement):void
/**
* Notify the host that an element properties have changed.
*
* @param e The element that has changed properties.
*/
public function graphicElementPropertiesChanged(e:IGraphicElement):void
/**
* Notify the host that an element size has changed.
*
* @param e The element that has changed size.
*/
public function graphicElementSizeChanged(e:IGraphicElement):void
/**
* Notify the host that an element layer has changed and it needs
* the shared sequence to be re-calculated.
* @param e The element that has layers size.
*/
public function graphicElementLayerChanged(e:IGraphicElement):void
/**
* Removes the element's <code>DisplayObject</code> from this <code>Group's</code>
* display list.
*
* The <code>Group</code> also ensures any elements that share the
* <code>DisplayObject</code> will be redrawn.
*
* <p>This method doesn't necessarily trigger new <code>DisplayObject</code>
* reassignment for the passed in <code>element</code>.
* To request new display object reassignment, call the
* <code>graphicElementLayerChanged</code> method.</p>
*
* @param element The graphic element whose display object will be discarded.
* @see #graphicElementLayerChanged
*/
public function discardDisplayObject(element:IGraphicElement):void
...........
}
I implemented a prototype of a custom IGraphicElement that shares the DisplayObject. I also did a prototype of updating the current GraphicElement to implement the proposed interface. I like it.
We should not see degradation of GraphicElement perf. when implementing this interface.
When implementing this DisplayObject sharing between GraphicElements will need to undergo testing.