Menu

IGraphicElement

SourceForge Editorial Staff

IGraphicElement - Functional and Design Specification


Glossary


  • Shared DisplayObject - this is a DisplayObject that is being drawn to by multiple separate graphic elements. For example three rectangular graphic elements can draw to the same Sprite.
  • Sequence - this is a sequence of one or more graphic elements that share the same DisplayObject. The Group maintains the graphic elements in sequences.
  • Shared index - this is the index of a DisplayObject in its sequence.
  • Rendering order - the order in which the children of a container render. This is well-defined at all times.

Summary and Background


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 Group can host GraphicElements.
  • GraphicElements can share DisplayObjects (in most common cases - no mask, no rotation, no 3D, etc.)
  • The Group manages when the DisplayObjects are created and at what index they are added.
  • The Group organizes the GEs into shared sequences.

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.

Usage Scenarios


Enable implementation of lighter-weight VisualElements that:

  • Share a DisplayObject and draw in its graphics property
    • Some examples are Rect, Path, Line, BitmapImage, Ellipse
  • Don't share a DisplayObject, but instead creates/gets a DisplayObject from a different source
    • Some examples are VideoElement, SimpleText, RichText

Detailed Description


IGraphicElement Interface Goals and Requirements

  • The IGraphicalElement interface has to allow the Group to specify the shared DisplayObject.
    • set/get DisplayObject
  • Interface is simple and relatively easy to implement. It should not require too much special bookkeeping around the shared DisplayObject.
    • If a IGE is not going to share DOs, but only takes advantage of the Group's DO management, the it should not be required to have its DOs implement ISharedGraphicsDisplayObject.
    • Group maintains creation and assignment of the DisplayObject.
    • Group maintains a "sharedIndex" property on IGraphicElement - this index in the sequence of elements with shared DisplayObjects
    • Group makes sure to efficiently draw all elements of the sequence if any element has requested redrawing - the shared DisplayObject has minimal obligations to implement ISharedDisplayObject which has a single property "redrawRequested".
  • Allow custom implementations of IGraphicElement to have custom requirements for the shared DisplayObject (don't impose limits the type of the DisplayObject).
    • IGraphicElements perform the actual creation of the DisplayObjects whenever the Group demands a new one. Group is responsible to call this efficiently.
    • IGraphicElements can check the offered DisplayObject for their custom requirements and they can reject it, if it's not worthy - a method "canDrawToShared(displayObject:DisplayObject):Boolean". Group is responsible for calling this method efficiently with the correct arguments.
    • The GraphicElement subclasses have a minimal requirement for the graphics property - they need a DisplayObject that implements "ISharedGraphicsDisplayObject"
    • The GraphicElement classes should be able to share the DisplayObject of the Group itself - Group will implement "ISharedGraphicsDisplayObject" as well.
    • IGraphicElement can select to use a shared DO, but don't allow any following GE to use it (IGE can specify itself as the end of the sequence). This is useful in the case where an IGE uses the shared DO in such a way that any following IGEs can't correctly render into it, for example if an IGE adds child DOs to a shared DO, no other IGEs should render to the shared DO, as the rendering order would be wrong.
  • IGE can throw away a DO when it doesn't need it anymore.
  • IGE can request re-evaluation of the shared sequences.
  • IGE can request to be redrawn.

How Group Organizes the Sequences of GraphicElements

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.

Sketches of various GraphicElements and the corresponding Group and DisplayObject tree

  • The Group logic decides how to assign the DisplayObjects based on calls to the IGraphicElement.
  • The Elements don't allow sharing of DisplayObjects when they are going to set some specific property on the DisplayObject (example: alpha, scale, rotation, filters, mask, etc.)
  • Currently Group has some specific implementation around "layer" which produces sub-optimal DisplayObject sharing, but developers should not rely on the behavior, as it may change in the future.

Diagram 1:

  • The Rectangles draw to the Group's graphics directly
  • The Button is not a IGraphicElement, it's an UIComponent and it is added directly as a child of the Group
  • The Ellipse needs to render on top of the Button, so it's assigned its own DisplayObject (InvalidatingSprite).
  • Group.numElements = 4, Group.numChildren = 2, 3 DisplayObjects in total (excluding children of Button).

Diagram 2:

  • The Ellipse has been configured with layer="-1" property, so it needs to render first. Therefore it gets assigned a DisplayObject that's the first child of the Group.
  • The first two Rect elements render after the Ellipse and can share their own DisplayObject.
  • The last two Rect elements also can share a DisplayObject.
  • Note that when we have layer property, we don't necessarily implement the most optimal DisplayObject sharing, in fact the Group's logic of how to assign DisplayObject may change in future versions and could, for example, assign all the Rects a single DisplayObject.
  • Group.numElements = 5, Group.numChildren = 3, 4 DisplayObjects in total.

Diagram 3:

  • The Rects draw directly to the Group's graphics
  • The SimpleText generates a DisplayObject for each text line, and adds them as children of the Group.
  • The Last Rect has to render above the text, so it gets assigned its own DisplayObject (InvalidatingSprite).
  • Group.numElements = 4, Group.numChildren = 1 + n, where n is the number of text lines.

Diagram 4:

  • First Rect draws directly to the Group
  • Second and third Rect have properties that need to be set directly on a Display Object (alpha="0.5") and hence they get assigned their own DisplayObjects
  • The last two Rects share a DisplayObject
  • Group.numElements = 5, Group.numChildren = 3

Diagram 5:

  • The first Rect needs its own DisplayObject as it has alpha="0.5"
  • Second Rect gets assigned its own DisplayObject as the first Rect won't share its own.
  • The two SimpleText elements add their text lines directly to the Group
  • The last Rect also gets assigned its own DisplayObject as there is no available to share.
  • Group.numElements = 5, Group.numChildren = 3 + n, where n is the number of text lines.

Diagram 6:

  • The first Rect draws directly to the Group
  • The SimpleText has alpha="0.5" so it needs a DisplayObject to set the alpha on, therefore it gets assigned its own. The text lines are added as children to the DisplayObject.
  • Group.numElements = 2, Group.numChildren = 1 + n, where n is the number of text lines.

API Description


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

    ...........
}

Prototype Work


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.

Performance


We should not see degradation of GraphicElement perf. when implementing this interface.

Issues and Recommendations


QA


When implementing this DisplayObject sharing between GraphicElements will need to undergo testing.



Related

Wiki: Flex 4

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.