The MX architecture has drag and drop support. Components extending mx:ListBase have default implementation with suitable APIs and user hooks.
This feature deals with leveraging the MX DND architecture to provide implementation for default DND support for the Spark List component. In addition, new DND related APIs will be introduced to support the Spark's pillar principles of skinning and modularity.
1. DND between Halo/Spark Lists with same data format.
2. DND between Halo/Spark Lists with different data formats.
3. DND between multiple Halo/Spark Lists where only particular components have compatible data formats.
4. Customizing the drag indicator (the appearance of the item renderers for the dragged items)
6. Specifying the default dragIndicator Class
7. Subclassing the default drag indicator class
8. Customizing the drop indicator.
9. Add DND support for a custom layout class.
The Spark List will implement the familiar DND APIs from the MX ListBase. These APIs will be hidden/unavailable for the DropDownList class. The APIs will pretty much follow the MX ListBase class DND related APIs, more detail in the API section.
Two cases to note with the default implementation of the ListItemDragProxy:
GroupBase will define overlay APIs. The overlay is a virtual display list of DisplayObjects that always render on top of the rest of the group's elements. This feature is required for the display of a drop indicator. It will also be used internally for mask, focus. Other use cases could be additional decorations like watermark for example.
The BasicLayout defines two sets of DND related APIs. To minimize the calculations for multiple calls between the List and the layouts a DropLOcation class is introduced.
When a List receives an event, it calls the layout to compute the DropLocation, examines the location and passes back the to the layout to request subsequent operations like displaying the drop indicator.
Public layout APIs, used by the Spark List, as well as other custom components that rely on layouts for DND functionality.
Protected layoutAPIs, used by the concrete Spark Layouts, as well as custom layouts to implement the DND functionality.
Note that some custom layouts may choose to override the method that instantiates the DropLocation in order to subclass it and add additional data.
The default Halo DND data format is "items". It is represented by a handler that returns and Array of the selected items in reverse order of their selection.
The default Spark DND data format is going to be "orderedItems", and "orderedItemsCaretIndex"
Compatibility:
// SparkList alaredy extends UIComponent, so it inherits the DragEvent metadata:
// <a href="Event%28name%3D%26quot%3BdragEnter%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragEnter", type="mx.events.DragEvent")</a>
// <a href="Event%28name%3D%26quot%3BdragOver%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragOver", type="mx.events.DragEvent")</a>
// <a href="Event%28name%3D%26quot%3BdragExit%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragExit", type="mx.events.DragEvent")</a>
// <a href="Event%28name%3D%26quot%3BdragDrop%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragDrop", type="mx.events.DragEvent")</a>
// <a href="Event%28name%3D%26quot%3BdragComplete%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragComplete", type="mx.events.DragEvent")</a>
// <a href="Event%28name%3D%26quot%3BdragStart%26quot%3B%2C%20type%3D%26quot%3Bmx.events.DragEvent%26quot%3B%29">Event(name="dragStart", type="mx.events.DragEvent")</a>
//--------------------------------------
// Styles
//--------------------------------------
/**
* The class to create instance of for the drag proxy during drag
* and drop operations initiated by the List.
*
* Must be of type <code>IFlexDisplayObject</code>.
*
* If the class implements the <code>ILayoutManagerClient</code> interface,
* then the instance will be validated by the DragManager.
*
* If the class implements the <code>IVisualElement</code> interface,
* then the instance's <code>owner</code> property will be set to the List
* that initiates the drag.
*
* The AIR DragManager takes a snapshot of the instance, while
* the non-AIR DragManager uses the instance directly.
*
* @default spark.components.supportClasses.ListItemDragProxy
*
*/
<a href="Style%28name%3D%26quot%3BdragIndicatorClass%26quot%3B%2C%20type%3D%26quot%3BClass%26quot%3B%2C%20inherit%3D%26quot%3Bno%26quot%3B%29">Style(name="dragIndicatorClass", type="Class", inherit="no")</a>
public class List extends ListBase implements IFocusManagerComponent
{
...
<a href="SkinPart%28required%3D%26quot%3Bfalse%26quot%3B%2C%20type%3D%26quot%3Bmx.core.IVisualElement%26quot%3B%29">SkinPart(required="false", type="mx.core.IVisualElement")</a>
// Should this be also a DisplayObject?
/**
* A skin part that defines a drop indicator that shows where the drag items
* will be inserted if the user releases the mouse to complete the drag drop operation.
*
* This is a dynamic skin part and must be of type IFactory.
*/
public var dropIndicator:IFactory;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
public function get dragEnabled():Boolean;
public function set dragEnabled(value:Boolean):void;
/**
* A flag that indicates whether items can be moved instead
* of just copied from the control as part of a drag-and-drop
* operation.
* If <code>true</code>, and the <code>dragEnabled</code> property
* is <code>true</code>, items can be moved.
* Often the data provider cannot or should not have items removed
* from it, so a MOVE operation should not be allowed during
* drag-and-drop.
*
* @default false
*/
public function get dragMoveEnabled():Boolean;
public function set dragMoveEnabled(value:Boolean):void;
public function get dropEnabled():Boolean;
public function set dropEnabled(value:Boolean):void;
//--------------------------------------------------------------------------
//
// Methods: Drag and drop
//
//--------------------------------------------------------------------------
/**
* Adds the selected items to the DragSource object as part of a
* drag-and-drop operation.
* Override this method to add other data to the drag source.
*
* @param dragSource The DragSource object to which to add the data.
*/
public function addDragData(dragSource:DragSource):void;
/**
* The default handler for the <code>dragStart</code> event.
*
* @param event The DragEvent object.
*/
protected function dragStartHandler(event:DragEvent):void
/**
* Handles <code>DragEvent.DRAG_COMPLETE</code> events. This method
* removes the item from the data provider.
*
* @param event The DragEvent object.
*/
protected function dragCompleteHandler(event:DragEvent):void
/**
* Handles <code>DragEvent.DRAG_ENTER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>DragManager.showDropFeedback()</code> method to set up the
* UI feedback as well as the <code>layout.showDropIndicator()</code> method
* to display the drop indicator and initiate drag scrolling.
*
* @param event The DragEvent object.
*/
protected function dragEnterHandler(event:DragEvent):void
/**
* Handles <code>DragEvent.DRAG_OVER</code> events. This method
* determines if the DragSource object contains valid elements and uses
* the <code>showDropFeedback()</code> method to set up the UI feedback
* as well as the layout's <code>showDropIndicator()</code> method
* to display the drop indicator and initiate drag scrolling.
*
* @param event The DragEvent object.
*/
protected function dragOverHandler(event:DragEvent):void
/**
* Handles <code>DragEvent.DRAG_EXIT</code> events. This method hides
* the UI feedback by calling the <code>hideDropFeedback()</code> method
* and also hides the drop indicator by calling the layout's
* <code>hideDropIndicator()</code> method.
*
* @param event The DragEvent object.
*
*/
protected function dragExitHandler(event:DragEvent):void
/**
* Handles <code>DragEvent.DRAG_DROP events</code>. This method hides
* the drop feedback by calling the <code>hideDropFeedback()</code> method.
*
* <p>If the action is a <code>COPY</code>,
* then this method makes a deep copy of the object
* by calling the <code>ObjectUtil.copy()</code> method,
* and replaces the copy's <code>uid</code> property (if present)
* with a new value by calling the <code>UIDUtil.createUID()</code> method.</p>
*
* @param event The DragEvent object.
*
* @see mx.utils.ObjectUtil
* @see mx.utils.UIDUtil
*
*/
protected function dragDropHandler(event:DragEvent):void
...
}
public interface IItemRenderer extends IDataRenderer, IVisualElement
{
...
/**
* True, when the ItemRenderer is displaying the item in
* a drag proxy, while the item is being dragged.
*
*/
function get dragging():Boolean;
function set dragging(value:Boolean):void;
...
}
// And the resulting states are:
<s:ItemRenderer focusEnabled="false"
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<s:states>
<s:State name="normal" />
<s:State name="hovered" />
<s:State name="selected" />
<s:State name="normalAndShowsCaret" />
<s:State name="hoveredAndShowsCaret" />
<s:State name="selectedAndShowsCaret" />
<s:State name="dragging" />
</s:states>
...
</s:ItemRenderer>
package spark.components.supportClasses
{
/**
* The default drag proxy used when dragging from a list-based control.
* A drag proxy is a component that parents the objects
* or copies of the objects being dragged.
*
* <p><code>List</code> instantiates this class and sets the owner property
* to point back to the <code>List</code>.
* This class, when validated by the DragManager, looks up its <code>owner</code>
* property and creates item renderers for all the selected visible items in
* the owner <code>List</code>.</p>
*
* <p>Creates copies of the item renderers and adds them as children in createChildren().
* Override the createChildren() method to insert additional initialization logic.</p>
*
* <p>The drag initiator component creates an instance of the ListItemDragProxy.
* The owner property is set to the drag initiator component.</p>
*/
public class ListItemDragProxy extends Group
{
include "../../core/Version.as";
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
public function ListItemDragProxy();
}
}
package spark.layouts.supportClasses
{
import flash.geom.Point;
import mx.core.IVisualElement;
import mx.events.DragEvent;
/**
* This class is used to store information related to a <code>DragEvent</code>
* in order to minimize calculations as a <code>DragEvent</code>
* related requests are passed back and forth between the <code>List</code> and
* its layout.
*
* The <code>DropLocation</code> is created by the <code>LayoutBase</code>
* class when the <code>List</code> calls the layout's
* <code>calculateDropLocation()</code> method in response to a <code>DragEvent</code>.
*
* The DropLocation is used by the layout for operations such as
* calculating the drop indicator bounds and drag-scroll deltas.
*
*/
public class DropLocation
{
/**
* Constructor.
*/
public function DropLocation()
{
}
/**
* The <code>DragEvent</code> associated with this location.
*/
public var dragEvent:DragEvent = null;
/**
* The drop index corresponding to the event.
*/
public var dropIndex:int = -1;
/**
* The event point in local coordinates of the layout's target.
*/
public var dropPoint:Point = null;
/**
* The element appearing immediately before the dropIndicator.
* This is usually used by the layout to calculate drag-scrolling
* deltas.
* Note that even when an element exists, this field may still be null
* in cases where the layout can't scroll in that direction.
*/
public var elementBefore:IVisualElement = null;
/**
* The element appearing immediately after the dropIndicator.
* This is usually used by the layout to calculate drag-scrolling
* deltas.
* Note that even when an element exists, this field may still be null
* in cases where the layout can't scroll in that direction.
*/
public var elementAfter:IVisualElement = null;
}
}
package spark.layouts.supportClasses
{
public class LayoutBase
{
...
//--------------------------------------------------------------------------
//
// Methods: Drag and drop, public APIs
//
//--------------------------------------------------------------------------
/**
* Sets the DisplayObject for this layout to use when
* displaying the drop indicator during drag and drop operation.
*
* Typically developers don't access this property directly,
* but instead rely on the List's default <code>DragEvent</code>
* handlers.
*
* <p>The <code>List</code> sets this property in response to a
* <code>DragEvent.DRAG_ENTER</code> event.
* The <code>List</code> initializes this property with an
* instance of its <code>dropIndicator</code> skin part.
* The <code>List</code> clears this property in response to a
* <code>DragEvent.DRAG_EXIT</code> event.</p>
*/
public function get dropIndicator():DisplayObject;
public function set dropIndicator(value:DisplayObject):void;
/**
* CAlculates the <code>DropLocation</code> for the specified
* <code>dragEvent</code>.
*
* @param dragEvent The dragEvent dispatched by the DragManager.
*
* @return Returns the DropLocation for this event, or null if the drop
* operation is not possible.
*
* @see #showDropIndicator
* @see #hideDropIndicator
*/
public function calculateDropLocation(dragEvent:DragEvent):DropLocation;
/**
* Sizes, positions and parents the dropIndicator for the specified
* DropLocation.
*
* Starts/stops drag-scrolling when necessary conditions are met.
*
* The <code>dorpIndicator</code> must be previously set.
*
* @param dropLocation <p> The DropLocation must be
* previously calculated through the calculateDropLocation() method.</p>
*
* @return Returns whether the drop indicator is visible.
*
* @see #hideDropIndicator
* @see #calculateDropLocation
* @see #dropIndicator
*/
public function showDropIndicator(dropLocation:DropLocation):Boolean;
/**
* Hides the previously shown <code>dropIndicator</code>,
* removes it from the display list and also stops the drag-scrolling.
*
* @see #showDropIndicator
* @see #dropIndicator
*/
public function hideDropIndicator():void;
//--------------------------------------------------------------------------
//
// Methods: Protected APIs for easier custom layout DND support
//
//--------------------------------------------------------------------------
/**
* Returns the index where a new item should be inserted if
* the user releases the mouse at the specified coordinates
* while completing a drag and drop gesture.
*
* Called by the <code>calculatedDropLocation()</code> method.
*
* @param x The x coordinate of the drag and drop gesture, in target's
* local coordinates.
*
* @param y The y coordinate of the drag and drop gesture, in target's
* local coordinates.
*
* @return The drop index or -1 if the drop operation is to available
* at the specified coordinates.
*
* @see #calculateDropLocation
*/
protected function calculateDropIndex(x:Number, y:Number):int;
/**
* Calculates the bounds for the drop indicator UI that provides visual feedback
* to the user of where the items will be inserted at the end of a drag and drop
* gesture.
*
* Called by the <code>showDropIndicator()</code> method.
*
* @param dropLocation A valid <code>DropLocation</code> previously calculated
* through the <code>calculateDropLocation</code> method.
*
* @return The bounds for the drop indicator or null.
*
* @see spark.layouts.supportClasses.DropLocation
* @see #calculateDropIndex
* @see #calculateDragScrollDelta
*/
protected function calculateDropIndicatorBounds(dropLocation:DropLocation):Rectangle;
/**
* Calculates how much to scroll for the specified DropLocation during a drag and drop gesture.
*
* Called by the <code>showDropIndicator()</code> method, as well as during drag-scrolling.
*
* @param dropLocation A valid <code>DropLocation</code> previously calculated
* through the <code>calculateDropLocation</code> method.
*
* @return The scroll delta point or null if there's no need to drag-scroll.
*
* @see spark.layouts.supportClasses.DropLocation
* @see #calculateDropIndex
* @see #calculateDropIndicatorBounds
*/
protected function calculateDragScrollDelta(dropLocation:DropLocation):Point;
}
}
package spark.components.supportClasses
{
public class GroupBase extends UIComponent implements IViewport
{
...
/**
* The overlay plane for this Group.
* All objects of the overlay plane render on top of the Group elements.
* Don't hold on to this object, as Group destroys and creates it on demand.
*/
public function get overlay():DisplayPlane
}
}
/**
* A DisplayPlane class maintains ordered list of DisplayObjects sorted on
* depth.
* Developers don't instantiate this class, but use the <code>overlay</code>
* property of <code>Group</code> and <code>DataGroup</code>.
*
* @see spark.components.Group#overlay
* @see spark.components.DataGroup#overlay
*/
public class DisplayPlane extends EventDispatcher
{
public function DisplayPlane()
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
/**
* Number of objects in the DisplayPlane.
*/
public function get numDisplayObjects():int
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* Returns the requested object with the specified index in the
* ordered list.
*/
public function getDisplayObjectAt(index:int):DisplayObject
/**
* Adds a <code>displayObject</code> with the specified depth to the ordered list.
* The position of the <code>displayObject</code> in the sorted lists is based on
* its depth, the object will be inserted after all objects with less than or equal
* depth value.
*
* @return Returns the index of the object.
*/
public function addDisplayObject(displayObject:DisplayObject, depth:Number = OverlayDepth.TOPMOST):int
/**
* Removes the specified <code>displayObject</code> from the sorted list.
*/
public function removeDisplayObject(displayObject:DisplayObject):void
}
package spark.components.supportClasses
{
/**
* The OverlayDepth class defines the default depth values for
* various overlay elements.
*
* @see spark.components.Group#overlay
* @see spark.components.DataGroup#overlay
*/
public final class OverlayDepth
{
/**
* The overlay depth for a drop indicator.
*/
public static const DROP_INDICATOR_DEPTH:Number = 100;
/**
* The overlay depth for a mask object.
*/
public static const MASK_DEPTH:Number = 200;
/**
* The overlay depth for a focus object.
*/
public static const FOCUS_DEPTH:Number = 300;
/**
* The default top most overlay depth.
*/
public static const TOPMOST:Number = 10000;
}
}
}