This class handles the drawing of the bits and pieces for all of the components I have made. It means that I don't need to import any graphics - we are making everything with pure code, out of thin air as it were. Skinning traditional components has taken a few different shapes over the years, but really, it was always a pain. Drawing your own graphics from scratch makes it easy to set up the colors, change the thickness, length, or even the shape of your component pieces. Suddenly skinning is no longer a separate task.

The ShapeFactoryComp class consists entirely of public functions. Once you create an instance of this class - private var sf:ShapeFactoryComp = new ShapeFactoryComp(); - then you access the functions like so: somePiece = sf.createSpriteWithLine(). Out of the many functions here, the XSlider class only uses a couple of them, which we will look at below.

public function createHorzGradWithLine(ca:Array, lineColor:Number, lineWidth:int, w:int, h:int, x:int = 0, y:int = 0):Sprite{
    var s:Sprite = new Sprite();
           
  var fillType:String = GradientType.LINEAR;
    var colors:Array = ca;
    var alphas:Array = [1, 1];
    var ratios:Array = [0, 255];
    var matrix:Matrix = new Matrix();
    var gradWidth:Number = w;
    var gradHeight:Number = h;
    var gradRotation:Number = 90 / 180 * Math.PI;
    var gradOffsetX:Number = 0;
    var gradOffsetY:Number = 0;
           
    matrix.createGradientBox(gradWidth, gradHeight, gradRotation, gradOffsetX, gradOffsetY);
    var spreadMethod:String = SpreadMethod.PAD;
    s.graphics.beginGradientFill(fillType, colors, alphas, ratios, matrix, spreadMethod);
    s.graphics.lineStyle(lineWidth, lineColor, 1);
    s.graphics.drawRect(0, 0, w, h);
    s.graphics.endFill();
    s.x = x;
  s.y = y;
  return s;        
}


This is the function we use to draw our track. Of the seven possible properties you can pass in, only the first five are required. You need to supply an array of two colors, a single color for the lineColor, an integer for the lineWidth, and an integer for the width - w - and the height - h. As you can see this function returns a Sprite, so the first thing we do is create a new Sprite. Then we have a bunch of variables connected with creating a gradient. Then using the graphics property of our Sprite - s - we begin the gradient fill, set up our lineStyle, draw the rectangle and finish with the endFill. Then we set the x and y properties of our Sprite and finally we return it.

The next function we use creates our triangleIcon.

public function createSliderTriangle(ca:Array, lc:Number, d:int = 10):Sprite{
    var tn:Sprite = new Sprite();
    var fillType:String = GradientType.LINEAR;
    var colors:Array = ca;
    var alphas:Array = [1, 1];
    var ratios:Array = [0, 255];
    var matrix:Matrix = new Matrix();
    var gradWidth:Number = d;
    var gradHeight:Number = d;
    var gradRotation:Number = 90 / 180 * Math.PI;
    var gradOffsetX:Number = 0;
    var gradOffsetY:Number = 0;
           
    matrix.createGradientBox(gradWidth, gradHeight, gradRotation, gradOffsetX, gradOffsetY);
    var spreadMethod:String = SpreadMethod.PAD;
    tn.graphics.beginGradientFill(fillType, colors, alphas, ratios, matrix, spreadMethod);
    tn.graphics.lineStyle(1, lc, 1);
    tn.graphics.moveTo(0, -(d/2));
    tn.graphics.lineTo((d/2), d/2);
    tn.graphics.lineTo(-(d/2), d/2);
    tn.graphics.lineTo(0, -(d/2));
    tn.graphics.endFill();
    return tn;
}


The function createSliderTriangle has only 3 possible properties you can manipulate, and only the first two are required. In the demo we have a variable called iconDiameter and we include it in the function to give us the flexibility to change the size of the triangle icon easily. Once again you provide an array of two colors, a single color for the line color - lc - and an optional diameter for the triangle. It works in much the same way as the last function we looked at. Create a new Sprite, set the variables for the gradient, start drawing with beginGradientFill, and set up our lineStyle. The actual drawing of the triangle shape comes up next. I want the triangle to have a centered registration point. To get ready to draw we move our line to a position where x = 0 and y = the property d divided by two and set to be negative. Using the default value of 10, this means that our first line will be drawn starting at x:0, y:-5. The first line is drawn to the co-ordinates x:d/2 or 5, y: d/2 or 5. This gives us the right hand side of the triangle. Our next co-ordinates are x:-(d/2) or - 5, y:d/2 or 5. This is the bottom of the triangle. Our last co-ordinates are x:0, y:-(d/2) or -5. This is the left hand side of the triangle and now we are back to the beginning, leaving us with the triangle's center point being 0, 0. The reason this is desirable is because I want the triangle's tip to match up with the scale lines. so, at the 0 point you notice that the left hand side of the triangle is hanging over the end, but the triangle's point is dead on 0. We end the fill and return the finished sprite.

The other class we will look at is the SliderBubble class which provides us with the bubble announcing the current position of the triangleIcon on the slider track.

package  ca.xty.components{
    import flash.display.*;
    import flash.events.*;
    import flash.text.*;
    import flash.geom.Matrix;
    import caurina.transitions.Tweener;


No surprises here. Our package declaration puts this class in the components folder. The usual suspects are imported, with the addition of the Tweener tweening classes.

public class SliderBubble extends Sprite{
       
    private var _bubbleColor:Number;
    private var _lineColor:Number;
    private var _bubbleWidth:int;
    private var _bubbleText:String;
    private var _align:String;
    private var _txtSize:int;
    private var _lineSize:int;
       
    private var popUpPanel:Sprite;
    private var bg:Sprite;
   
    private var ppTF:TextField;
    private var bubbleFormat:TextFormat;
       
    private var sf:ShapeFactoryComp = new ShapeFactoryComp();


We create the variables we will be needing next. They are all designated as private since we will only be using within this class. The first group of variables are all preceded by an underscore. This is simply my system for identifying which variables are passed in via the constructor function.

public function SliderBubble(BubbleColor:Number, LineColor:Number, BubbleWidth:int, BubbleText:String, Align:String = "center", TxtSize:int = 9, LineSize:int = 2) {
           
    _bubbleColor = BubbleColor;
    _lineColor = LineColor;
    _bubbleWidth = BubbleWidth;
    _bubbleText = BubbleText;
    _align = Align;
    _txtSize = TxtSize;
    _lineSize = LineSize;
           
    bubbleFormat = new TextFormat();
    bubbleFormat.font = "verdana";
    bubbleFormat.size = _txtSize;
    bubbleFormat.color = 0x000000;
    bubbleFormat.align = _align;


We receive our properties via the constructor and assign them to the local private variables we created a moment ago. Then we set up our TextFormat, bubbleFormat.

popUpPanel = new Sprite();
    addChild(popUpPanel);
    // Initially we set the popUpPanel's alpha to 0
    popUpPanel.alpha = 0;
    //This is our main background - the one whose colors you set up in the constructor
    bg = sf.createSpriteWithLine(_bubbleColor, _lineColor, _lineSize, _bubbleWidth, 20);
    bg.x = 0;
    bg.y = 0;
    popUpPanel.addChild(bg);
           
    ppTF = new TextField();
    ppTF.x = bg.x + 3;
    ppTF.y = bg.y + 2;
    ppTF.width = bg.width - 6;
    ppTF.height = bg.height - 4;
    ppTF.autoSize = TextFieldAutoSize.CENTER;
    ppTF.text = _bubbleText;
    ppTF.setTextFormat(bubbleFormat);
    popUpPanel.addChild(ppTF);
           
    if(ppTF.width > bg.width - 6){
        bg.width = ppTF.width + 6;
        ppTF.x = bg.x + 3;
    }
           
    Tweener.addTween(popUpPanel, {alpha:1, time:1, transition:"easeOutExpo"});
}


The first piece to make is a Sprite to hold all the pieces. Using this as a container it makes it easy to dispose of everything when the time comes. For the moment we want this to be invisible, so we set the popUpPanel's alpha property to zero.

Next we draw the background, making use of of ShapeFactoryComp class once again. We set the background's x and y positions to zero and add it as a child of the popUpPanel.

Now it's time for our TextField. To create a bit of margin we set the TextField's x property to bg.x plus 3, and it's y property to bg.y plus 2. Keeping these margins in mind, we make the width of the TextField to be the bg.width minus 6. That's a margin of 3 on the left and the right. The height is calculated in the same fashion using bg.height minus 4. Because we will be passing different and unknown numbers into this TextField we set it's autoSize property to CENTER. This means that if there is too much text to fit in the default size it will expand equally on the left and the right. Then we set the text property to use the text we passed in as _bubbleText, and apply the TextFormat bubbleFormat. We also add this as a child of the popUpPanel.

The if statement will monitor the size of the TextField, and if it is larger than the background minus the total margin of 6, then it will adjust the size of the background and make it the width of the TextField plus 6. Then it re-centers the TextField by setting it's x property to the bg.x plus 3.

Now that everything is in place, we use Tweener to change the alpha property from 0 to 1.

public function updateSliderTxt(nt:String):void{
    ppTF.text = nt;
    ppTF.setTextFormat(bubbleFormat);
    if(ppTF.width > (bg.width - 6)){
        bg.width = ppTF.width + 6;
        ppTF.x = bg.x + 3;
    }else if(ppTF.width < (bg.width - 6)){
        bg.width = ppTF.width + 6;
        ppTF.x = bg.x + 3;
    }
}


The public function updateSliderTxt provides a means to update the sliderBubble's text without redrawing the bubble itself. We pass in the new text value as a parameter of the function and assign it to the TextField. We set the TextFormat property again and then set about checking whether the TextField is either larger than the background, or smaller than the background and adjust things accordingly.

That's It - For Now

The XSlider has quite a bit of versatility, but there is always more you can do. Customizing this for your own needs is really quite simple. Let's say you find yourself needing a slider in a place where there is a dark background. You can alter the colors of everything - except the text. Currently the scaleFormat's color property is hard coded to be black. To make this more flexible and expose the text color property you can simply create a public variable. We'll call it txtColor and set it's default color to black. Now, in the scaleFormat, change the scaleFormat.color property to be: scaleFormat.color = txtColor;. So far nothing has changed to affect any other instance of the XSlider class you are using. But now you can set the text color because the txtColor variable is public. This is just one of many properties you can expose. Often times these classes evolve for me as specific needs arise. In a more complex modification example, let's say that you need a volume slider. You already have most of what you need, but it might be nice to use a different kind of graphic in place of the scale. We want to add a triangle of sorts that starts out thin on the left and rises up as we move right. That's pretty simple, right? Well, it is, but you need to provide a few other things as well. For instance, we'll need a Boolean property we'll call useAsVolume. This will let us know whether or not we need to draw the special volume graphic. And speaking of that, we'll need a Sprite we'll call volumeBG. This will require a new function in our ShapeFactoryComp class we could call createVolumeBG. So with just a few changes in place we have transformed our slider into a workable volume slider. 

As always, if you have any questions, just ask away!