Monday, January 25, 2010

Custom Buttons In Flex - Drop Shadow on Text



Just recently I was asked about adding a drop shadow to the text in a button.  Going through a few blogs, I found quickly that the best example to follow was from some guys who're way more advanced than me (http://www.davidflatley.com/2007/12/17/programmatic-button-skins-in-flex-3/, http://www.asfusion.com/blog/entry/stateful-skins-in-flex-3e-color-transitions-in-buttons-now-possible, http://www.tink.ws/blog/stateful-skins-in-flex/).  Essentially, the label inside a button is a IUITextField.  It can have filters, styles, whatever you want.  So I simply extended the Button class in .as, and added my own 'filters = [new DropShadowFilter(x,y)]', and when I implement the button, I get text w/ drop shadows.  More excitingly, I can setup some local vars that can be adjusted at runtime, so not every custom button has to have a drop filter, or if I click on it, I can quickly, in a programmatic skin, remove the drop shadow on over or down states.  This means I now have some cooler looking buttons that can shift text formatting dynamically (which is so much better than hardcoding in the style - just in case I'd like to reuse my buttons!)

Here's some code:

In the class itself, I have to override the rollover and rollout so I can remove the drop shadow to give a different dynamic effect.  Also, I have to override the updateDisplayList because the disabled buttons shouldn't have a drop shadow (again, just for fun).

/**Class**/
package com.studioNorth.skins{
    import flash.display.DisplayObject;
    import flash.events.MouseEvent;
    import flash.filters.DropShadowFilter;
   
    import mx.controls.Button;
    import mx.core.IUITextField;
    import mx.core.UITextField;

    public class CustomButtonTextDropShadow extends Button{
        private var ds:DropShadowFilter; //This could be public so users could access and change it or any other variable
       
        public function CustomButtonTextDropShadow(){
            super();
            ds = new DropShadowFilter(1);
        }
       
        override protected function rollOverHandler(event:MouseEvent):void{
            textField.filters = null;
            super.rollOverHandler(event);
        }
        override protected function rollOutHandler(event:MouseEvent):void{
            super.rollOutHandler(event);
            if(enabled)
                textField.filters = [ds];
        }
       
        override protected function createChildren():void{
            if (!textField){
                textField = IUITextField(createInFontContext(UITextField));
                textField.filters = [ds];
                textField.styleName = this;
                addChild(DisplayObject(textField));
            }
        }
       
        override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void{
            super.updateDisplayList(unscaledWidth, unscaledHeight);
            if(!this.enabled){
                textField.filters = null;
            }
        }

    }
}


/**Main App**/

   


before mouse over:



during mouse over:



that simple!

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);
    }

  }
}

Friday, January 15, 2010

Eclipse Navigator Link with Editor

This as a small setting that I made about 6 months ago, forgot where I did it, and about 2 days later, regretted ever making this change.  I hate having my navigator bounce around all the time every time I change a tab, so this was annoying as all get out.  Stoopid me, I didn't find it again until my co-worker showed me this link.  It was for something completely different (i.e. hiding .svn folders, but on a Windows machine, however on my mac svn files are hidden in Eclipse).  Needless to say, I saw that example image, with the term 'Link with Editor'.  My eyes misted and I got very smiley, as my co-worker accidentally solved my problem (and he just wanted to help me hide the hidden .svn folders).  Oh well.  Glad I'm back on a more efficient route!

Wednesday, January 13, 2010

Flex Builder error: The project properties files could not be read.

My recent flex adventure almost came to a crashing end when I shut down and restarted Eclipse, only to find that my file's project properties could no longer be read?!?!  I got very angry trying to figure out what happened, thinking to myself that I recently tried committing my project to SVN via subclipse (which is another story on its own).  One thing I'm very happy I did was create a backup before trying to commit.  Pretty much every time I tried to open my project I got the project properties files could not be read.  I looked at the .actionScript and .project files, and couldn't see anything missing.  I looked high and low, and ran a -diff on my project and the backup copy.  Turns out, I was missing the .flexProperties file.  How that got deleted from the project I don't know, but it was horrifyingly maddening to only get a mystic message saying the project properties files could not be read.  Which files?!?  A better message would be preferred, but alas, I copied the old over to the project folder, and I'm back in shape.  Lesson learned, if you get this error, make sure you have all three project files in the correct location (aka CYA).

Thanks to Ben Clinkenbeard and Eric Hélier for getting me in the right direction!

Monday, January 11, 2010

Massive amounts of memory consumption via image swapping in Flex

On my project, I was using De MonsterDebugger (DMD) (great tool, easy to implement, but can get heavy if you don't have a lot of RAM - peaks at about 150MB for me, which can hurt because I'm running on only 2 GB).  In my project, I am rolling over canvases, and on each rollover, the background of the application should have its images change.  My initial development had me doing this by changing the style of the Application (application.setStyle("backgroundImage",assets/myNewImg)).  And to make the transition smooth, I had a second Image that I was using on top of the background, but underneath all the other components (z level = lowest possible). 

The problem quickly became clear (even more so w/ DMD because I could see why my computer would stop responding when debugging, i.e. my RAM usage was spiking).  In the last several days I have been reading about garbage collection (GC) and how it seems to act on its own accord.  It was happening w/ my app, which would always return to about 40MB in runtime, but could (and often did) spike upwards of 250MB!!!  All that for a single swf file running in Safari.  I checked my objects, commented out different things (including custom turtle border graphics), and went through item by item that could've ran up so much RAM. 

Finally it came back to my images being swapped in and out.  I thought the image class could handle changing sources quickly, but it turns out, that was eating huge resources to render the image (all while doing some lightweight tweening).  I tried my best to build this application to follow OOP and reusability as best as I could.  The data source (an xml config file) is what drives the layout and information of the app, so I wanted to avoid embedding anything that needed to be called up explicitly w/ a case/if.  But alas, I could only do so much before I had to make some exceptions in my class and embed the background images, and use a case statement to assign it correctly on hover.  Once here, and once I realized that changing two background images (one stacked on the other to allow me to transition smoothly - read fade - meant I needed two images). 

My app now only spikes at about 68MB, no matter how many transitions I make, and I don't have to try to force GC (which eventually happens during idle time).  The last thing I think I'd like to try is possibly improving the cpu performance and keeping the fan from getting hot by adjusting frame rate, but for now, I'm happy that my app doesn't spike so much anymore.

Thursday, January 7, 2010

Smooth scrolling of a horizontallist

So I tried various makes of horizontal scrolling a horizontallist.  I followed these examples, yet either my canvas never added the hlist (because the canvas initialized after I added the hlist, so the hlist was garbage collected before it's parent was initialized), or the scroll bar never moved the list (although I came close to rewriting the scrollhandler function.  Suffice it to say, finally my hack involved adding the hlist to my canvas through a calllater(addHList), and then having external buttons be able to scroll my hlist left and right a designated amount based not on the scroll bar of the parent canvas, but on the x coordinate of my hlist.  Simple enough!  I don't need to worry about overriding the location of the scroll bar, and now i can use something like tweenmax to handle the transitioning of the horizontal lists position, so i get scrolling, and external buttons to move my hlist, and i get all the benefits of an hlist (which has as many columns as i need because i set its columncount = dataprovider.length.  if anyone wants to see code, let me know and i'll post an update.

 private function scrollHorizontalList(e:Event):void{
                var dir:String = e.currentTarget.name;
                var end:int;
                var pos:int = hList.x;
                var max:int = hList.dataProvider.length;
                var myTween:TweenMax;
                trace(dir);
                if(dir == "rButton"){
                    end = pos + colWidth;
                    if(!TweenMax.isTweening(hList)){
                        //if(hList.x-colWidth*colsToScroll)
                        myTween = new TweenMax(hList,1,{x: hList.x - colWidth*colsToScroll, ease:Cubic.easeInOut});
                        myTween.play();
                        hListScroll+=colsToScroll;
                    }
                } else if (dir == "lButton") {
                    end = pos - colWidth;
                    if(!TweenMax.isTweening(hList)){
                        myTween = new TweenMax(hList,1,{x: hList.x + colWidth*colsToScroll, ease:Cubic.easeInOut});
                        myTween.play();
                        hListScroll-=colsToScroll;
                    }
                }
                lButton.enabled = (hListScroll > 0)?true:false;
                rButton.enabled = (hListScroll < max-colsToScroll)?true:false;
            }

Tuesday, January 5, 2010

Eclipse .ini changes

FB3 breaks then fixes, all because I forgot an 'm' in my init file.  Actually, I was trying to change the min, max, and permanent sizes of my eclipse runtime, and I accidentally deleted one of the 'm's, and then eclipse never started or worked for me again.  I got really angry, wasted 30 minutes trying to figure out what happened, including trying to read the logs and see where it went wrong, and finally decided to skip ahead and reinstall eclipse (3.4.2 of course, as FB3 won't work on 3.5, although some have figured it out…)  So long story short, I reinstalled, did the software update to re-attach FB3 plugin (the new 3.5 sdk rocks!), and then couldn't get SVN to attach via subclipse.  Another half hour later, and I returned to my .ini file, checked it against my old install, found the missing 'm', almost kicked myself, fixed the 'm', and now I have everything back in order…how annoying!  Although, now Aptana makes things a bit slower...anyone else having that problem?