Friday, January 29, 2010

Drawing Dashed Lines, Arcs, and Cubic Curves

The ActionScript Graphics class supports drawing solid lines and quadratic curves. But there isn't native support for dashed lines or cubic (Bezier) curves.

I've created a sample application that shows the three line types: straight, quadratic curve and cubic curve. It also lets you choose between showing a solid line, a dashed line, or both. The line thickness can also be adjusted.



I also got a little carried away and added the quadratic and cubic control points as buttons that you can drag around the canvas to change the curve.

Most of the drawing functionality for these examples is in the GraphicsUtils.as class that contains the following static functions:
  • drawLine() - draws a straight line, either solid (using Graphics.lineTo() function) or dashed
  • drawQuadCurve() - draws a quadratic curve, either solid (using Graphics.curveTo() function) or dashed
  • drawCubicCurve() - draws a cubic curve (two control points), either solid or dashed. The curve is an approximation done by dividing the curve into many small segments and drawing straight lines
  • drawCircle() - draws a circle, either solid (using Graphics. drawCircle() function) or dashed
  • drawArc() - draws an arc, either solid or dashed
The dashed lines are drawn by dividing the line into many small segments to draw the dashes. By default a dash length of 10 pixels is used, but that is customizable.

The equations used to calculate the values at any point along the line for the three cases were found on the Wikipedia entry for Bézier curves.

Tuesday, January 26, 2010

Flex Context Menus

I've had some frustration with Flex ContextMenus, so I thought I'd write an entry about the basics of creating context menus, adding items, listening for context menu events, and a few restrictions on context menus.

Every InteractiveObject (e.g. Application, Panel, Button, etc) contains a contextMenu property. For most components it will be null and you'll have to create a new menu and assign it to that property. So you can have different context menus for different components.

Here is a simple example of a context menu set on the application:


Here is a short snippet from the above example showing you have to create a ContextMenu, hide the built-in menu items, and listen for menu events.
private function initContextMenu():void {
    // 1. Create the context menu if it doesn't exist
    // it will exist for the application, but won't for most other components
    if (!contextMenu) {
        this.contextMenu = new ContextMenu();
    }

    // 2. Hide the built-in menu items 
    // (you can't remove the basic 3 or 4 items like: Settings, About, etc)
    contextMenu.hideBuiltInItems();
    
    // 3. Add menu items, you can optionally set the menu item enablement and
    // visibility in the ContextMenuItem constructor
    var firstItem:ContextMenuItem  = new ContextMenuItem("Hello there!"true);
    contextMenu.customItems.push(firstItem);
    var secondItem:ContextMenuItem = new ContextMenuItem("Disabled");
    contextMenu.customItems.push(secondItem);
    var thirdItem:ContextMenuItem = new ContextMenuItem("Hidden");
    contextMenu.customItems.push(thirdItem);
    
    // 4. Listen for menu events
    // this event happens when the context menu is about to show
    contextMenu.addEventListener(ContextMenuEvent.MENU_SELECT, 
        function(event:ContextMenuEvent):void {
            // add logic here to determine the visibility or enablement
            secondItem.enabled = false;
            thirdItem.visible = false;
        });
    
    // 5. Handle menu item click events
    var handler:Function = function(event:ContextMenuEvent):void {
        Alert.show("You clicked on the menu item '" 
                    event.currentTarget.caption + "'");
    };
    firstItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handler);
    secondItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handler);
    thirdItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, handler);
}

There are many restrictions to Flex ContextMenus, some of which will drive you crazy. Read carefully, it will save you time later.
  • Maximum of 15 custom menu items in the menu
  • No sub-menus allowed
  • No icons in menus
  • Menu items must be 100 characters or less
  • Control characters, newlines, and other white space characters are ignored
  • MANY reserved words including (but not limited to):
    • Save
    • Zoom In
    • Zoom Out
    • 100%
    • Show All
    • Quality
    • Play
    • Loop
    • Rewind
    • Forward
    • Back
    • Movie not loaded
    • About
    • Print
    • Show Redraw Regions
    • Debugger
    • Undo
    • Cut
    • Copy
    • Paste
    • Delete
    • Select All
    • Open
    • Open in new window
    • Copy link
    • Copy Link Location
    • Del
I'm not quite sure why there are so many restrictions, but I'm sure Adobe has a good reason for it!

More details about the restrictions can be found on the ContextMenuItem page.

There are a few other solutions for people who want more control over the menus. These usually involve adding right click listeners in JavaScript on top of the Flex application and passing that event into Flex to position and show a custom menu instead of the usual Flex context menu.
Here is one such solution: Custom Context Menu.
These solutions can work quite effectively, but since they depend on the browser they can be quite buggy.