Saturnboy
 8.5

UPDATE: I have another Drag-and-Drop Revisited post that covers even more drag-and-drop functionality available in Flex 4.

The Flex 4 gods were kind to us developers when they made the great decision to leave the custom drag-and-drop support unchanged. We just do what we’ve always done: detect the user is trying to drag something via mouseDown or mouseMove and then add both dragEnter and dragDrop event handlers to the drop target. So there is nothing in this post that’s not basically identical to Flex 3, except the coolness of FXG (which you can easily mimic with Degrafa in Flex 3).

Simple Drag-and-Drop

I’ll begin with a basic Flex 4 application. We have two draggable graphics (a Rect and an Ellipse) in the left panel, and a target panel on the right:

<?xml version="1.0" encoding="utf-8"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark">
 
    <fx:Script>
        <![CDATA[
            imports...
 
            private function mouseDownHandler(e:MouseEvent):void {
                DragManager.doDrag(e.currentTarget as IUIComponent, null, e);
            }
 
            private function dragEnterHandler(e:DragEvent):void {
                DragManager.acceptDragDrop(e.currentTarget as IUIComponent);
            }
 
            private function dragDropHandler(e:DragEvent):void {
                e.currentTarget.addElement(e.dragInitiator);
            }
        ]]>
    </fx:Script>
 
    <s:Panel title="src" width="100" minHeight="133" x="10" y="10">
        <s:Graphic width="80" height="80"
                mouseDown="mouseDownHandler(event)">
            <s:Rect ... </s:Rect>
        </s:Graphic>
 
        <s:Graphic width="80" height="80"
                mouseDown="mouseDownHandler(event)">
            <s:Ellipse ... </s:Ellipse>
        </s:Graphic>
        ...
    </s:Panel>
 
    <s:Panel title="target" width="100" minHeight="133" x="120" y="10"
            dragEnter="dragEnterHandler(event);"
            dragDrop="dragDropHandler(event);">
        ...
    </s:Panel>
</s:Application>

Reading the code above, each draggable Graphic has a mouseDown handler that calls DragManager.doDrag() to initiate dragging. And the target Panel calls DragManager.acceptDragDrop() on dragEnter and addElement() on dragDrop. Note that since Panel is a Spark container we must use addElement() to re-parent the dropped graphic (the familiar addChild() is still used for Halo containers).

Our simple drag-and-drop app (view source enabled):

Flash is required. Get it here!
Using a FXG Drag Proxy

In the example above, the moment you start dragging a component the drag proxy is displayed. By default, the proxy is just a bounding rectangle with an alpha value of 0.5. This is particularly noticeable and lame when you try to drag the pink circle. Thankfully, we can use any display object for the drag proxy (more specifically any component that implements IFlexDisplayObject which includes UIComponent and any of its descendants). Why not use a FXG graphic? It is, after all, a major piece of the new hotness that is Flex 4.

First, we need to modify the mouseDownHandler to instantiate our FXG graphic. Then we just add it as the forth parameter to the DragManager.doDrag() call.

private function mouseDownHandler(e:MouseEvent):void {
    var star:Star = new Star();
    DragManager.doDrag(e.currentTarget as IUIComponent, null, e, star);
}

And here is Star.mxml:

<?xml version="1.0" encoding="utf-8"?>
<s:Graphic
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        width="80" height="80">
 
    <s:Path data="M 119,0 L 148,86 238,86 166,140 192,226 119,175 46,226 72,140 0,86 90,86 Z"
            y="2" scaleX="0.3361345" scaleY="0.3361345">
        <s:fill>
            <mx:SolidColor color="#FFCC00" />
        </s:fill>
    </s:Path>
</s:Graphic>

Bang! We have a gold star as our drag proxy. Try it out for yourself (view source enabled):

Flash is required. Get it here!
Dynamic Drag Proxy

Dragging a gold star is pretty cool, but how can we make the drag proxy look exactly like the drag source? For that we need a dynamic drag proxy.

First, for reasons which will become clear in a moment, we need to revise our app to make each of the drag source graphics into a separate component. So the left Panel changes to contain these custom components:

<s:Panel title="src" width="100" minHeight="133" x="10" y="10">
    <graphics:Square fillColor="#6666FF"
            mouseDown="mouseDownHandler(event)" />
 
    <graphics:Circle fillColor="#FF66FF"
            mouseDown="mouseDownHandler(event)" />
 
    <graphics:Star fillColor="#FFCC00"
            mouseDown="mouseDownHandler(event)" />
 
    <graphics:Square fillColor="#66FF99"
            mouseDown="mouseDownHandler(event)" />
 
    <s:layout>
        <s:VerticalLayout gap="10" horizontalAlign="center" paddingTop="10" paddingBottom="10" />
    </s:layout>
</s:Panel>

Next, we again modify the mouseDownHandler, this time we instantiate our drag proxy dynamically. We use reflection on the incoming drag source to get its name, instantiate it, adjust a few properties, and pass it to the DragManager.doDrag() call.

private function mouseDownHandler(e:MouseEvent):void {
    var dragSrc:Graphic = e.currentTarget as Graphic;
 
    //create a proxy by creating a new "copy" of the drag src
    var className:String = getQualifiedClassName(dragSrc);
    var klass:Class = getDefinitionByName(className) as Class;
    var proxy:* = new klass();
 
    //set the proxy's properties to match the src + sexy drop shadow
    proxy.width = dragSrc.width;
    proxy.height = dragSrc.height;
    proxy.fillColor = (dragSrc as IDraggableGraphic).fillColor;
    proxy.filters = [new DropShadowFilter()];
 
    DragManager.doDrag(dragSrc, null, e, proxy);
}

The real reason for the reflection gymnastics, plus the need to package each drag source as a custom component, is the lack of a deep copy operation on UIComponent. If we could just clone the drag source, we’d be golden. Alas, for many reasons, the most obvious of which is that it is a huge pain in the ass, there is no such thing as deep copy. In our case, my work around was to package all the visual stuff of each draggable item into a single custom component with a known API (all the custom graphics implement IDraggableGraphic). Then I reflect, instantiate, configure, and pass to doDrag().

Our dynamic drag proxy now matches the drag source, plus the sexy drop shadow. Check it out (view source enabled):

Flash is required. Get it here!
Files

NOTE: All code was built with Flash Builder 4 Beta 1.




 6.10

The dream of SVG was probably born sometime in the late 90′s. Version 1.0 arrived in 2001, followed by the current version, SVG 1.1, in 2003. For anyone keeping score at home, that’s over 6 years ago, which is like 120 years ago in internet dog years. Also for those at home, SVG is a portable XML-based vector graphics format (it also does raster graphics), but you’d probably not be reading this if you didn’t already know.

Nowadays, I’m finally seeing some SVG in use. It works natively in all the real browsers (obviously not IE, which requires a plugin), and even on some mobile devices. But most importantly to me, I see it in my day-to-day work in Degrafa, and with the beta release of Flex 4, in Catalyst and FXG.

The vast majority of my experience in SVG is with paths (SVG Spec, § 8) and to a lesser extent Transforms (§ 7) and Filters (§ 15). Thankfully, these are some of the most useful and important pieces of SVG, and they all have nice one-to-one mappings to components in Degrafa and FXG.

SVG Path Primer

In SVG, a path is the outline of some object. It is described as a series of segments, where each segment can be different, either a line, curve, or arc. Path data is most often given in shorthand syntax as a series of commands followed by coordinates (we’ll ignore the long form for now). Let’s illuminate the discussion with some examples.

SVG:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
    "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
    width="200" height="200">
  <path d="M 0,0 L 100,0 L 100,100 L 0,100 z"
      fill="#EECCEE"
      stroke="#FF00FF"
      stroke-width="3" />
</svg>

Degrafa in Flex 3:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
        xmlns:mx="http://www.adobe.com/2006/mxml"
        xmlns:Degrafa="http://www.degrafa.com/2007"
        layout="absolute">
 
    <Degrafa:GeometryComposition graphicsTarget="{[cnv]}">
        <Degrafa:Path data="M 0,0 L 100,0 L 100,100 L 0,100 z">
	        <Degrafa:fill>
	            <Degrafa:SolidFill color="#EECCEE" />
	        </Degrafa:fill>
	        <Degrafa:stroke>
	            <Degrafa:SolidStroke color="#FF00FF" weight="3" />
	        </Degrafa:stroke>
        </Degrafa:Path>
    </Degrafa:GeometryComposition>
 
    <mx:Canvas id="cnv" />
</mx:Application>

MXML Graphics (aka FXG) in Flex 4:

<?xml version="1.0" encoding="utf-8"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/halo">
 
    <s:Graphic>
        <s:Path data="M 0,0 L 100,0 L 100,100 L 0,100 z">
            <s:fill>
                <mx:SolidColor color="#EECCEE" />
            </s:fill>
            <s:stroke>
                <mx:SolidColorStroke color="#FF00FF" weight="3" />
            </s:stroke>
        </s:Path>
    </s:Graphic>
</s:Application>

The examples above use the shorthand path syntax to draw a 100px square that starts at the coordinate origin (0,0), which is the upper left corner in SVG and Flex. Beware the coordinate origin when translating Inkscape SVG to Flex. The other interesting thing to note is the amazing similarity between Degrafa and FXG. Who knew all my time learning Degrafa will instantly translate to Flex 4 and FXG? Awesome!

SVG Path Shorthand

Here’s a quick overview of shorthand syntax for SVG path data:


Move
M <x,y>
Move the pen to the given point.

Line
L <x,y>+
Draw a line to given point. Multiple points may be specified to draw polyline.

Horizontal Line
H <x>
Draw a horizontal line to given coordinate.

Vertical Line
V <y>
Draw a vertical line to given coordinate.

Quadratic Bezier
Q <cx,cy x,y>+
Draw a quadratic Bezier curve to given coordinate using a control point. Multiple Beziers may be specified to draw polycurve.

Cubic Bezier
C <cx1,cy1 cx2,cy2 x,y>+
Draw a cubic Bezier curve to given coordinate using two control points. Multiple Beziers may be specified to draw a polycurve.

Arc
A <rx,ry rot,lrg,swp x,y>
Draw elliptical arc to the given point.

Close
Z
Close the path.

Alas, the beta version of FXG does not support the Arc segment type, which I suspect is due to lack of support for arbitrary arcs in the underlying Flash Player rendering engine but I don’t know for sure. Thankfully, Degrafa offers full arc support (thanks Greg!). If you really need to draw arcs in FXG, for stuff like pie wedges, and are unafraid of getting into a cage match with your trigonometry textbook, you can do a good job approximating arcs with cubic Bezier curves. Alternately, you can just use Degrafa once it gets ported to Flex 4. Lastly, using uppercase for the segment type specifies absolute coordinates. This is the format commonly used by Illustrator and Inkscape when exporting drawings to SVG. One can easily switch to relative coordinates by just switching the commands to lowercase, but I would try to avoid it if at all possible as it tends to make one’s head hurt.

The Many Shapes of a Square

All of the squares above, use this shorthand data:

M 0,0 L 100,0 L 100,100 L 0,100 z

First, a Move to set the pen at the origin. Then, a Line right to (100,0), followed by a Line down to (100,100), followed by a Line left to (0,100). Then, a close (z) to return to the origin.

I can drop the commas if I want:

M 0 0 L 100 0 L 100 100 L 0 100 z

Or drop all but the first Line to make a polyline:

M 0,0 L 100,0 100,100 0,100 z

Or use Horizontal Line and Vertical Line:

M 0,0 H 100 V 100 H 0 z

Or even use relative coordinate (which makes my head hurt a little):

m 0,0 l 100,0 l 0,100 l -100,0 z
Curves

Straight lines are cool, but the real fun in life lies in the curves. Cubic Bezier curves should be very familiar to anyone who’s used a vector drawing program. Let’s replace the first segment in our square with a cubic Bezier segment. Now, the SVG shorthand becomes:

M 0,0 C 25,-25 50,25 100,0 L 100,100 L 0,100 z

When rendered, we get this:

square-funny

The shorthand command says curve to (100,0), but start out heading towards control point #1 at (25,-25) and end up coming in from control point #2 at (50,25).

Arcs

Again, let’s replace the first segment in our square with an arc segment. Now, the SVG shorthand becomes:

M 0,0 A 50,25 0 0,1 100,0 L 100,100 L 0,100 z

When rendered, we get this:

square-arc

The shorthand command says arc to (100,0), with an x-radius of 50 and a y-radius of 25, with a rotation of 0. The large-arc and sweep flags are a little confusing so you’ll want to review the SVG Spec, § 8.3.8 if you need to get down and dirty with arcs.

Conclusion

I’m a firm believer in “right tool for the job.” So, when in comes to getting SVG path data into Flex, I’m definitely going to use Illustrator or Inkscape as much as possible, and in the future I might just use Catalyst for everything. But there are a few important situations where the Flex developer absolutely must know SVG. First, if you want to do any kind of path morphing (like this), you’ll need precision control over your path segments. And second, if you want to do any dynamic path generation (like building a multi-level radial menu on the fly – which sounds like a good topic for a future post), you’ll need to manually construct your SVG paths.

Files

© 2017 saturnboy.com