Thursday, January 21, 2010

drawRoundRect in Flex is nice, but what about non-straight rectangular borders

rounded rectangles are nice, but...

Everyone wants at least one rounded rectangle corner these days in Flex…well, it's easy to do if you just want to define the corner radius for each corner.  However, what do you do if you want to notch a side, extend another side, and still have the corners?  Well, just drawing a rounded rectangle doesn't work, because you're now changing the shape of the object. Now I've gotta use the graphics.curveTo, moveTo, and lineTo methods to give me a cooler rectangular border (this might even work well for tabNavigatorButtons)

Because I'm big into programmatic skinning (it mainly just makes sense to me not to have to hardcode a project to specific images imported/exported from Flash or Illustrator or whatnot.  I'd like my skins to be able to redraw themselves quickly and efficiently.  For my project, I wanted to create rounded rectangles, but also have the notch.  Using the drawRoundRectangle was a start, but I couldn't change anything else in the border.  I searched and came across this guys article about dynamically creating shapes based on where you click on the canvas.  Amazing idea which would be really neat to extend to the use of manipulating images or cutting anything on the canvas (think lasso or scissor tool in any image editing software by Adobe)

Anyways, to create the rectangle with rounded corners and non-straight borders, I abused the example from Adobe and added my own flair. You need the plotPoints part to make the rectangle draw everything, including the corners.  In addition, I had to adjust for my 3 rounded corners, which include curveTo functionality.  That was the trickier part, but in the end, got me a programmatic skin with notches…yippee!!!

package your.package
{
 
 
  import flash.filters.BitmapFilterQuality;
  import flash.filters.DropShadowFilter;
  import flash.geom.Point;
  import flash.geom.Rectangle;
 
  import mx.graphics.RectangularDropShadow;
  import mx.skins.RectangularBorder;

  public class CustomContainerBorderSkin extends RectangularBorder {

    private var dropShadow:RectangularDropShadow;
    private var _points:Array = [];
    private var rect:Rectangle;

    // Constructor.
    public function CustomContainerBorderSkin() {
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{

           super.updateDisplayList(unscaledWidth, unscaledHeight);

           var cornerRadius:Number = getStyle("cornerRadius");
           var backgroundColor:int = getStyle("backgroundColor");
           var backgroundAlpha:Number = getStyle("backgroundAlpha");
       
        graphics.clear();
          
           var indent:Number = 10;
        plotPoints(indent);
        graphics.beginFill(backgroundColor,1);
        graphics.moveTo(_points[0].x, _points[0].y);
        for (var i:int = 0; i < _points.length; i++) {
            if(i == 2 || i == 7 || i == 9){
                switch(i){
                    case(2):
                        graphics.curveTo(x + width, y, _points[i].x, _points[i].y);
                        break;
                    case(7):
                        graphics.curveTo(x + width, y + height, _points[i].x, _points[i].y);
                        break;
                    case(9):
                        graphics.curveTo(x, y + height, _points[i].x, _points[i].y);
                        break;
                }
            }else{
                graphics.lineTo(_points[i].x, _points[i].y);
            }
        }
        graphics.lineTo(_points[0].x, _points[0].y);
        graphics.endFill();
          parentObj = null;
    }
    private function plotPoints(n:Number):void {
        _points = [];
        _points.push(new Point(x, y));                                    //0 topLeft
        _points.push(new Point(x + width - 10    , y));                     //1 topRight curve from
        _points.push(new Point(x + width        , y + 10));             //2 topRight curve to
        _points.push(new Point(x + width        , y + height - 45));    //3 indent begin
        _points.push(new Point(x + width - n    , y + height - 35));    //4 indent point
        _points.push(new Point(x + width        , y + height - 25));    //5 indent end
        _points.push(new Point(x + width        , y + height - 10));     //6 bottomRight curve from
        _points.push(new Point(x + width - 10    , y + height));           //7 bottomRight curve to
        _points.push(new Point(x + 10            , y + height));            //8 bottomLeft curve from
        _points.push(new Point(x                , y + height - 10));    //9 bottomLeft curve to
    }
   
    private function getBitmapFilter():DropShadowFilter {
        var distance:Number = 2;
        var angle:Number = 45;
        var color:Number = 0x000000;
        var alpha:Number = .5;
        var blurX:Number = 8;
        var blurY:Number = 8;
        var strength:Number = 0.65;
        var quality:Number = BitmapFilterQuality.LOW;
        var inner:Boolean = false;
        var knockout:Boolean = false;

        return new DropShadowFilter(distance, angle, color, alpha,
            blurX, blurY, strength, quality, inner, knockout);
    }

  }
}

2 comments:

  1. I need a button in the following shape, very rough.

    |------\
    |------/

    Basically, a rect with a point on the right side. I have this with a programmatic skin, but cannot get a corner radius on the left to look good. Using curveTo with no luck so far. Have you solved this problem with your rect?

    Thanks,
    -b

    ReplyDelete
  2. @b,

    I'm not sure what you're trying to accomplish. What left corner do you want rounded. By drawing the skin programmatically, you have complete control of curving the line to wherever you want to go.

    ReplyDelete