Another custom Flex preloader but with RSL support

Actionscript, Flex 1 Comment »

This post has a great article on building a custom Flex preloader which also allows you to handle the progress of the runtime shared libraries that the Flex framework now loads into the users cache.

The DownloadProgressBar component allows you to view the progress of the runtime shared libraries but be aware that it can only tell you about the RSL as it loads and as there may be more than one it is practically impossible to get any indication of how many bytes in total the app is going to load. For this reason you may have seen erratic preloaders out there at the moment using all kinds of colourful language to tell you what it’s doing … “loading assets”, “modules”, “opening the champagne” etc. This is all done to keep the user occupied while each of the elements are loaded.

Then there’s the actual SWF file. Thankfully there are separate events to handle this so if you prefer you can still display the progress of the main SWF and then when it is completed change your display to inform the user that you are “initialising” which shouldn’t be too long after the main SWF has finished. Indeed your dependencies may have finished before the SWF.

I guess the only way to do this accurately is to know up front exactly how many bytes your application is going to load and then as you get the information from ProgressEvent.bytesLoaded from both the SWF and the RSL you can adjust your percentage calculation accordingly. If anyone does know how you can programmatically detect this I’d love to hear it.

Thanks to Bernhard Hirschmann and Jesse Warden for the work they’ve done and purely for my own reference here’s the class …

Custom Preloader for Flex with RSL support

  1. package
  2. {
  3.     import flash.display.MovieClip;
  4.     import flash.display.Sprite;
  5.     import flash.events.Event;
  6.     import flash.events.ProgressEvent;
  7.    
  8.     import mx.events.FlexEvent;
  9.     import mx.events.RSLEvent;
  10.     import mx.preloaders.DownloadProgressBar;
  11.  
  12.     /**
  13.      * This class extends the lightweight DownloadProgressBar class.  This class
  14.      * uses an embedded Flash 8 MovieClip symbol to show preloading.
  15.      * For handling both, SWF and RSL download, it separated its download progress
  16.      * by handling the appropriate events, i.e. RSLEvent
  17.      *
  18.      * @author jessewarden
  19.      * @author Bernhard Hirschmann (http://coding.bhirschmann.de)
  20.      */   
  21.     public class Preloader extends DownloadProgressBar
  22.     {
  23.        
  24.         /**
  25.          * The Flash 8 MovieClip embedded as a Class.
  26.          */       
  27.         [Embed(source="../assets/flash/preloader.swf", symbol="Preloader")]
  28.         private var FlashPreloaderSymbol:Class;
  29.        
  30.         private var clip:MovieClip;
  31.        
  32.     private var    isRslDownloading:Boolean = false;
  33.    
  34.         private var rslPercent:Number = 0;
  35.         private var swfPercent:Number = 0;
  36.        
  37.         private var rslBytesTotal:Number;
  38.         private var rslBytesLoaded:Number = 0;
  39.        
  40.         private var swfBytesTotal:Number;
  41.         private var swfBytesLoaded:Number = 0;
  42.        
  43.         public function Preloader()
  44.         {
  45.             super();
  46.            
  47.             // instantiate the Flash MovieClip, show it, and stop it.
  48.             // Remember, AS2 is removed when you embed SWF's,
  49.             // even "stop();", so you have to call it manually if you embed.
  50.             clip = new FlashPreloaderSymbol();
  51.             addChild(clip);
  52.             clip.gotoAndStop("start");
  53.         }
  54.        
  55.        public override function set preloader(preloader:Sprite):void
  56.     {                   
  57.       trace("starting...");
  58.  
  59.       // runtime shared library
  60.       preloader.addEventListener( RSLEvent.RSL_PROGRESS, onRSLDownloadProgress );
  61.       preloader.addEventListener( RSLEvent.RSL_COMPLETE, onRSLDownloadComplete );
  62.       preloader.addEventListener( RSLEvent.RSL_ERROR, onRSLError );
  63.  
  64.       // application
  65.       preloader.addEventListener( ProgressEvent.PROGRESS, onSWFDownloadProgress );   
  66.       preloader.addEventListener( Event.COMPLETE, onSWFDownloadComplete );
  67.      
  68.       // initialization
  69.       preloader.addEventListener( FlexEvent.INIT_PROGRESS, onFlexInitProgress );
  70.       preloader.addEventListener( FlexEvent.INIT_COMPLETE, onFlexInitComplete );
  71.      
  72.       clip.preloader.rsl_amount_txt.text = clip.preloader.app_amount_txt.text = "0%";
  73.      
  74.       centerPreloader();
  75.     }
  76.        
  77.     /**
  78.      * Makes sure that the preloader is centered in the center of the app.
  79.      *
  80.      */       
  81.     private function centerPreloader():void
  82.         {
  83.             x = (stageWidth / 2) - (clip.width / 2);
  84.             y = (stageHeight / 2) - (clip.height / 2);
  85.         }
  86.        
  87.     /**
  88.      * Updates the progress bar.
  89.      */
  90.     private function updateProgress():void
  91.     {
  92.       var p:Number = Math.round( (rslPercent + swfPercent) / 2 );
  93.         clip.preloader.gotoAndStop(p);
  94.     }
  95.    
  96.         /**
  97.          * As the RSL (runime shared library) (frame 2 usually) downloads, this event gets called.
  98.          * You can use the values from this event to update your preloader.
  99.          * @param event
  100.          *
  101.          */       
  102.         private function onRSLDownloadProgress( event:ProgressEvent ):void
  103.     {
  104.         isRslDownloading = true;
  105.        
  106.         rslBytesTotal = event.bytesTotal;
  107.         rslBytesLoaded = event.bytesLoaded;
  108.         rslPercent = Math.round( (rslBytesLoaded / rslBytesTotal) * 100);
  109.         trace("onRSLDownloadProgress: rslBytesLoaded " + rslBytesLoaded);
  110.         trace("onRSLDownloadProgress: rslBytesTotal " + rslBytesTotal);
  111.         trace("onRSLDownloadProgress: " + rslPercent + "%");
  112.         clip.preloader.rsl_amount_txt.text = String(rslPercent) + "%";
  113.        
  114.         updateProgress();
  115.     }
  116.    
  117.     /**
  118.      * When the download of frame 2
  119.      * is complete, this event is called. 
  120.      * This is called before the initializing is done.
  121.      * @param event
  122.      *
  123.      */       
  124.     private function onRSLDownloadComplete( event:RSLEvent ):void
  125.     {
  126.         trace("onRSLDownloadComplete: 100% - bytes total: " + event.bytesTotal);
  127.            clip.preloader.gotoAndStop(100);
  128.         clip.preloader.rsl_amount_txt.text = "100%";
  129.         rslPercent = 100;
  130.     }
  131.  
  132.     private function onRSLError( event:RSLEvent ):void
  133.     {
  134.         trace("onRSLError: " + event.errorText + " - " + event.url);
  135.         clip.preloader.status_txt.text = event.errorText;
  136.     }
  137.  
  138.         /**
  139.          * As the SWF (frame 2 usually) downloads, this event gets called.
  140.          * You can use the values from this event to update your preloader.
  141.          * @param event
  142.          *
  143.          */       
  144.         private function onSWFDownloadProgress( event:ProgressEvent ):void
  145.     {
  146.         swfBytesTotal = event.bytesTotal;
  147.         swfBytesLoaded = event.bytesLoaded;
  148.        
  149.         if ( isRslDownloading ) {
  150.           // as soon as RSL starts downloading the SWF data are added by the RSL values
  151.           swfBytesTotal -= rslBytesTotal;
  152.           swfBytesLoaded -= rslBytesLoaded;
  153.         }
  154.         swfPercent = Math.round( (swfBytesLoaded / swfBytesTotal) * 100);
  155.         trace("onSWFDownloadProgress: " + swfPercent + "%");
  156.         trace("onSWFDownloadProgress: swfBytesLoaded " + swfBytesLoaded);
  157.         trace("onSWFDownloadProgress: swfBytesTotal " + swfBytesTotal);
  158.         clip.preloader.app_amount_txt.text = String(swfPercent) + "%";
  159.  
  160.         updateProgress();
  161.     }
  162.    
  163.     /**
  164.      * When the download of frame 2
  165.      * is complete, this event is called. 
  166.      * This is called before the initializing is done.
  167.      * @param event
  168.      *
  169.      */       
  170.     private function onSWFDownloadComplete( event:Event ):void
  171.     {
  172.         trace("onSWFDownloadComplete: 100%");
  173.            clip.preloader.gotoAndStop(100);
  174.         clip.preloader.app_amount_txt.text = "100%";
  175.         swfPercent = 100;
  176.     }
  177.    
  178.     /**
  179.      * When Flex starts initilizating your application.
  180.      * @param event
  181.      *
  182.      */       
  183.     private function onFlexInitProgress( event:FlexEvent ):void
  184.     {
  185.         //trace("onFlexInitProgress: Initializing...");
  186.         try {
  187.           clip.preloader.gotoAndStop(100);
  188.           clip.preloader.status_txt.text = "Initializing...";
  189.         }
  190.         catch (e:Error) {
  191.         }
  192.     }
  193.    
  194.     /**
  195.      * When Flex is done initializing, and ready to run your app,
  196.      * this function is called.
  197.      *
  198.      * You're supposed to dispatch a complete event when you are done.
  199.      * I chose not to do this immediately, and instead fade out the
  200.      * preloader in the MovieClip.  As soon as that is done,
  201.      * I then dispatch the event.  This gives time for the preloader
  202.      * to finish it's animation.
  203.      * @param event
  204.      *
  205.      */       
  206.     private function onFlexInitComplete( event:FlexEvent ):void
  207.     {
  208.         trace("onFlexInitComplete");
  209.         clip.addFrameScript(21, onDoneAnimating);
  210.         clip.gotoAndPlay("fade out");
  211.     }
  212.    
  213.     /**
  214.      * If the Flash MovieClip is done playing it's animation,
  215.      * I stop it and dispatch my event letting Flex know I'm done.
  216.      * @param event
  217.      *
  218.      */       
  219.     private function onDoneAnimating():void
  220.     {
  221.         trace("onDoneAnimating");
  222.         clip.stop();
  223.         dispatchEvent( new Event( Event.COMPLETE ) );
  224.     }
  225.        
  226.     }
  227. }

Custom Preloader class for Flex apps

Actionscript, Flex No Comments »

I’ve blogged on this before but here’s an updated version of a custom preloader class that extends Sprite and loads in a MovieClip from a SWC to use as the animation and display the load progress.

Custom preloader for Flex

  1. package com.cravens.nr.preloader
  2. {
  3.         import com.cravens.nr.model.Config;
  4.        
  5.         import flash.display.GradientType;
  6.         import flash.display.Sprite;
  7.         import flash.events.Event;
  8.         import flash.events.ProgressEvent;
  9.         import flash.events.TimerEvent;
  10.         import flash.geom.Matrix;
  11.         import flash.utils.Timer;
  12.        
  13.         import mx.events.FlexEvent;
  14.         import mx.preloaders.IPreloaderDisplay;
  15.  
  16.         public class NRPreloader extends Sprite implements IPreloaderDisplay
  17.         {
  18.                 protected var loadingPanel:BusyPanel;
  19.                 protected var timer:Timer;
  20.                
  21.                 public function NRPreloader()
  22.                 {
  23.                         super();
  24.                 }
  25.                 //------------------------------------------------------------------------------
  26.                 public function set preloader(preloader:Sprite):void
  27.                 {
  28.                         preloader.addEventListener(ProgressEvent.PROGRESS, handleProgress);
  29.                         preloader.addEventListener(Event.COMPLETE, handleComplete);
  30.                        
  31.                         preloader.addEventListener(FlexEvent.INIT_PROGRESS, handleInitProgress);
  32.                         preloader.addEventListener(FlexEvent.INIT_COMPLETE, handleInitComplete);
  33.                 }
  34.                 //------------------------------------------------------------------------------
  35.                 public function initialize():void
  36.                 {
  37.                         var matrix:Matrix = new Matrix(0,1,1,0,0,0);
  38.                        
  39.                         graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  40.                         graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);
  41.                        
  42.                         loadingPanel = new BusyPanel();
  43.                        
  44.                         try
  45.                         {
  46.                                 loadingPanel.heading_txt.text = "LOADING ... PLEASE WAIT";
  47.                                 loadingPanel.message_txt.text = "0/100%";
  48.                         }
  49.                         catch(e:Error)
  50.                         {
  51.                                 throw new Error("Asset does not contain the relevant textField objects");
  52.                         }
  53.                         finally
  54.                         {
  55.                                 loadingPanel.x = Config.VIEWPORT_CENTER_X-150;
  56.                                 loadingPanel.y = Config.VIEWPORT_CENTER_Y;
  57.                                
  58.                                 addChild(loadingPanel);
  59.                         }
  60.                 }
  61.  
  62.                
  63.                 //------------------------------------------------------------------------------
  64.                 // Define empty event listeners.
  65.                 private function handleProgress(event:ProgressEvent):void
  66.                 {
  67.                         var prog:Number = Math.ceil((event.bytesLoaded/event.bytesTotal)*100);
  68.                        
  69.                         loadingPanel.message_txt.text = prog+"/100%";
  70.                 }
  71.                 //------------------------------------------------------------------------------
  72.                 private function handleComplete(event:Event):void
  73.                 {
  74.                         loadingPanel.message_txt.text = "LAUNCHING";
  75.                 }
  76.                 //------------------------------------------------------------------------------
  77.                 private function handleInitProgress(event:FlexEvent):void
  78.                 {
  79.                 }
  80.                 //------------------------------------------------------------------------------
  81.                 private function handleInitComplete(event:Event):void
  82.                 {
  83.                         timer = new Timer(500,1);
  84.                         timer.addEventListener(TimerEvent.TIMER, dispatchComplete);
  85.                         timer.start();     
  86.                 }
  87.                
  88.                 //------------------------------------------------------------------------------
  89.                 private function dispatchComplete(event:TimerEvent):void
  90.                 {
  91.                         timer.removeEventListener(TimerEvent.TIMER, dispatchComplete);
  92.                         timer = null;
  93.                         dispatchEvent(new Event(Event.COMPLETE));
  94.                 }
  95.                
  96.                 //------------------------------------------------------------------------------
  97.                 // Implement IPreloaderDisplay interface
  98.                
  99.                 public function get backgroundColor():uint
  100.                 {
  101.                         return 0;
  102.                 }
  103.                 //------------------------------------------------------------------------------
  104.                 public function set backgroundColor(value:uint):void
  105.                 {
  106.                
  107.                 }
  108.                 //------------------------------------------------------------------------------
  109.                 public function get backgroundAlpha():Number
  110.                 {
  111.                         return 0;
  112.                 }
  113.                 //------------------------------------------------------------------------------
  114.                 public function set backgroundAlpha(value:Number):void
  115.                 {
  116.                 }
  117.                 //------------------------------------------------------------------------------
  118.                 public function get backgroundImage():Object
  119.                 {
  120.                         return undefined;
  121.                 }
  122.                 //------------------------------------------------------------------------------
  123.                 public function set backgroundImage(value:Object):void {
  124.                 }
  125.                 //------------------------------------------------------------------------------
  126.                 public function get backgroundSize():String {
  127.                         return "";
  128.                 }
  129.                 //------------------------------------------------------------------------------
  130.                 public function set backgroundSize(value:String):void {
  131.                 }
  132.                 //------------------------------------------------------------------------------
  133.                 public function get stageWidth():Number
  134.                 {
  135.                         return Config.VIEWPORT_WIDTH;
  136.                 }
  137.                 //------------------------------------------------------------------------------
  138.                 public function set stageWidth(value:Number):void
  139.                 {
  140.                 }
  141.                 //------------------------------------------------------------------------------
  142.                 public function get stageHeight():Number
  143.                 {
  144.                         return Config.VIEWPORT_HEIGHT;
  145.                 }
  146.                 //------------------------------------------------------------------------------
  147.                 public function set stageHeight(value:Number):void
  148.                 {
  149.                 }
  150.         }
  151. }

Also, notice I’ve created a simple grad background using the Sprite graphics …

  1. var matrix:Matrix = new Matrix(0,1,1,0,0,0);  
  2. graphics.beginGradientFill(GradientType.LINEAR,[0x000000,0x89265a],[1,1],[125,205],matrix);
  3. graphics.drawRect(0,0,Config.VIEWPORT_WIDTH,Config.VIEWPORT_HEIGHT);

This is the best way to do things because remember, the framework has not loaded while the preloader is displayed so you need to keep things lightweight and simple.

Also, the Matrix object rotates the grad so it runs vertically.

Have fun.

Detecting when text has run into the end of a container in a TextFlow object

Actionscript, Flex 3 Comments »

More fun and games with the Text Layout Framework. So, I have a load of links being flowed one by one through a bunch of linked containers. I need to detect when a link reaches the end and has ‘overflowed’ so to speak.

To do this you need to make sure your ContainerController objects have their ScrollPolicy set to OFF so the flowComposer recognises that the text is not in the container anymore. This will not halt the flow however so you need to add a nice bit of code that will detect if the FlowElement is no longer within a container.

Here’s the code …

Detecting when text has run into the end of the ContainerController in a TextFlow object

  1. /*
  2. A good idea to set a config object to pass into the TextFlow object when you create it
  3. */
  4. config = Configuration(TextFlow.defaultConfiguration).clone();
  5.  
  6. /*
  7. And add this overflowPolicy to it as well as your formatting defaults
  8. */
  9. config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
  10.  
  11. textFlow = new TextFlow(config);
  12.  
  13. /*
  14.   Create your containers from sprites and make sure the scrollPolicy is OFF
  15. */
  16. var controller:ContainerController = new ContainerController(containerSprite,width,height);
  17. controller.verticalScrollPolicy = controller.horizontalScrollPolicy = ScrollPolicy.OFF;
  18.                                        
  19. textFlow.flowComposer.addController(controller);
  20.  
  21. /*
  22. Now imagine we are in a loop which adds a paragraph of lorem ipsum to the textflow and updates the controllers and checks if the flow is now out of the bounds of the container
  23. */
  24.  
  25. while(LOOP)
  26. {
  27.   textFlow.addChild(myParagraphElement); // you've created the paragraph
  28.   textFlow.flowComposer.updateAllControllers(); // you've created the controllers
  29.  
  30.   var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  31.   if(containerIndex==-1) textFlowCompleteHandler();
  32. }

It’s these two lines that do the detection …

  1.  
  2. var containerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(textFlow.textLength-1);
  3.  
  4.   if(containerIndex==-1) textFlowCompleteHandler();

The findControllerIndexAtPosition method will return -1 if the character index passed in is not within a container. Remember to use textFlow.textLength-1 to get the last character. The textFlow variable is an instance of the TextFlow object.

The textFlowCompleteHandler can be any method that stops the insertion of FlowElement objects happening.

Crazy code

Actionscript, Flex No Comments »

Sometimes you have to write a method for something crazy complicated. In this snippet I had a container with a load of TextFlow text in it and I needed to zoom into a specific link within the flow while also scaling the container and positioning it within a Scroller viewport without the container going out of the bounds of the Viewport when the link is centered within it.

This is meaningless to anyone but me but sometimes you just have to share the pain.

Zoom in to a link in a TextFlow within a scaled container in a Scroller viewport

  1. private function moveAndZoomToLink(link:LinkElement,p:Person):void
  2.                         {
  3.                                 var linkStart:int = link.getAbsoluteStart();
  4.                                 var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
  5.                                 var linkPositionOnLine:int = linkStart-textFlowLine.absoluteStart;
  6.                                        
  7.                                 var controllerIndex:int = textFlow.flowComposer.findControllerIndexAtPosition(linkStart);
  8.                                 var controller:ContainerController = textFlow.flowComposer.getControllerAt(controllerIndex);
  9.                                 var containerSprite:Sprite = controller.container;
  10.                                
  11.                                 var rect:Rectangle = textFlowLine.getBounds();
  12.                                 var atomRect:Rectangle = textFlowLine.getTextLine().getAtomBounds(linkPositionOnLine+1);
  13.                                 var nameWidth:Number = p.firstName.length*atomRect.width;
  14.                                
  15.                                 var globalAtomRect:Rectangle = new Rectangle(rect.x+(atomRect.x-atomRect.width),rect.y,nameWidth,rect.height);
  16.                                
  17.                                 var box:Shape = new Shape();
  18.                                 box.graphics.beginFill(0xff0000,0.3);
  19.                                 box.graphics.drawRect(globalAtomRect.x,globalAtomRect.y,globalAtomRect.width,globalAtomRect.height);
  20.                                 containerSprite.addChild(box);
  21.                                
  22.                                 var localPosition:Point = new Point();
  23.                                 localPosition.x = containerSprite.x+rect.x+globalAtomRect.x;
  24.                                 localPosition.y = containerSprite.y+rect.y;
  25.                                
  26.                                 /*
  27.                                         We're zooming in so scale the local position
  28.                                 */
  29.                                 var scaledX:Number = localPosition.x*Config.SHIRT_ZOOM_SCALE;
  30.                                 var scaledY:Number = localPosition.y*Config.SHIRT_ZOOM_SCALE;
  31.                                
  32.                                 /*
  33.                                         We don't want the shirt to move so it is out of the viewport so calculate what this range is ...
  34.                                 */
  35.                                
  36.                                 var maxMoveX:Number = (Config.SHIRT_WIDTH*Config.SHIRT_ZOOM_SCALE)-Config.VIEWPORT_WIDTH;
  37.                                 var maxMoveY:Number = (Config.SHIRT_HEIGHT*Config.SHIRT_ZOOM_SCALE)-Config.VIEWPORT_HEIGHT;
  38.                                
  39.                                 var newX:Number = 0-scaledX;
  40.                                 var newY:Number = 0-scaledY;
  41.                                
  42.                                 var centeredX:Number = newX+Config.VIEWPORT_CENTER_X;
  43.                                 var centeredY:Number = newY+Config.VIEWPORT_CENTER_Y;
  44.                                
  45.                                 /*
  46.                                         Check the shirt hasn't moved out of bounds
  47.                                 */
  48.                                
  49.                                 var offsetX:Number = centeredX>0? centeredX*-1 : centeredX<0-maxMoveX? (centeredX*-1)-maxMoveX : 0;
  50.                                 var offsetY:Number = centeredY>0? centeredY*-1 : centeredY<0-maxMoveY? (centeredY*-1)-maxMoveY : 0;
  51.                                
  52.                                 /*
  53.                                         Finally, the new position is nudged so it is at the centre of the viewport space or as near as can be.
  54.                                 */
  55.                                 moveToPointX = centeredX>0? 0 : centeredX<0-maxMoveX? centeredX+((centeredX*-1)-maxMoveX) : centeredX;
  56.                                 moveToPointY = centeredY>0? 0 : centeredY<0-maxMoveY? centeredY+((centeredY*-1)-maxMoveY) : centeredY;
  57.                                
  58.                                 var viewportX:Number = Config.VIEWPORT_CENTER_X+offsetX;
  59.                                 var viewportY:Number = Config.VIEWPORT_CENTER_Y+offsetY;
  60.                                
  61.                                 shirtMoveTo.play();
  62.                                
  63.                                 var evt:PersonSelectionEvent = new PersonSelectionEvent(PersonSelectionEvent.PERSON_SELECTION,true);
  64.                                 evt.viewportPoint = new Point(viewportX,viewportY);
  65.                                 evt.person = p;
  66.                                
  67.                                 dispatchEvent(evt);
  68.                         }

Snippety Snipplr snip!

Actionscript No Comments »

So I’ve been using Snipplr recently for storing little code snippets. Looks like a pretty good service but after I tested its embedding features recently in a couple of posts I got sad and started to cry. This is because it didn’t embed very well. Not very well at all. You couldn’t scroll the code horizontally to read it.

Well, I’ve now installed the Snipplr wordpress plugin so hopefully the next bunch of stuff you see will be a couple of Snipplr snippets nicely embedded with some lovely scrolly-pollys scrollbars. Ok, I’ll stop talking like a baby now.

Get the server/domain name that your SWF is running on

  1. /*
  2. In Flex the Application needs to be complete so run this code within your applicationComplete event handler and pass a Boolean into a Data model somewhere so you can use it elsewhere.
  3. */
  4.  
  5. var Flex3Application:String = URLUtil.getServerName(Application.application.loaderInfo.url);
  6.  
  7. var Flex4Application:String = URLUtil.getServerName(FlexGlobals.topLevelApplication.loaderInfo.url);

Detecting if your SWF is running over HTTP or locally

  1. /*
  2. In Flex the Application needs to be complete so run this code within your applicationComplete event handler and pass a Boolean into a Data model somewhere so you can use it elsewhere.
  3. */
  4.  
  5. var isLocal:Boolean = loaderInfo.url.indexOf("file") == 0;

There’s a TextMate bundle too.

Exciting ay? The fun never stops here!

How to install the AIR 2 Beta SDK into FlashBuilder 4 and build a cool multi-touch app for your MacBook

Actionscript, Flex No Comments »

Phew … that was a long blog post title! I have to be very specific about this little demo though because if you aren’t on a MacBook with a multi-touch trackpad this demo will not do anything and then you’ll be severely pissed! I’ve not done any tests with Windows so I’m not sure about touch screens or other AIR enabled devices. More information on this here.

Firstly, go and download the latest AIR 2 SDK Beta from here.

The first thing you need to do is duplicate your FB4 SDK folder which you will find in the Application/Adobe Flash Builder 4/sdks/ folder. Then copy the tbz2 download into that folder.

4.0.0 AIR 2 Beta
Uploaded with plasq‘s Skitch!

Now you need to uncompress the tbz2 (zip) to the duplicated SDK folder you created. You’ll need to do this in the Terminal app so open it and type ‘cd’ to change the directory and then drag the folder into the terminal to add the path. Then hit return to jump into that folder and enter this text to unzip the package …

tar jxvf AIR20_mac_sdk_*****.tbz2

… replace the asterisks with the version number of the beta you downloaded. You’ll have something like this in your terminal window :

Terminal 2014 bash 2014 80Õ²4
Uploaded with plasq‘s Skitch!

Then hit return and you should see all the files being installed into the appropriate folders.

Now open Flash Builder 4 and go to the preferences to change the SDK. Choose the new SDK folder you duplicated which has now got the new AIR 2 packages installed.

Preferences
Uploaded with plasq‘s Skitch!

Now create a new Flex AIR project and go to the project preferences to choose your new compiler SDK:

Properties for MultiTouchTest_html
Uploaded with plasq‘s Skitch!

Now you are ready to build a new AIR 2 project with cool multi-touch events! And here’s one I made earlier which demos four of the new TransformGestureEvent Events that you can play with just using the MacBook multi-touch trackpad. It’s basically a small little app of a Monkey that you can scale with a pinch and rotate with a two finger twist. You can also drag him around with a two-finger pan and also make him spring up, down, left and right by swiping on the track pad with three fingers.

Let’s jump straight into the code. I’ve built this in Flex so I use MXML to generate my application window and add a simple Sprite object to the stage using SpriteVisualElement. There’s a simple SWC that contains a Sprite that I created in Flash and exported as ‘Monkey’. This can then be added to the SpriteVisualElement object as a child.

Here’s the full Application class :

View the full source and download the project here.

Grab the AIR file and play on your desktop here. You’ll need the AIR 2 runtime that you can grab here.

If you want to install your tests on an iPhone that’s a blog post for the near future … you can’t do it from within FB4 at the moment and I aint got CS5. My attempts at doing it from the command line failed but I think that’s because it doesn’t like the SWF that FB4 produced. Will look into it.

Hope you enjoy touching my monkey you dirty boys and girls!

Multiple links in a TextFlow object in Flex 4

Actionscript, Flex No Comments »

Flex 4 includes the new Adobe Text Layout Framework (TLF) which is a fantastic new addition that allows greater control of text rendering and the flow of text through containers. You can view the demo material here.

I recently had to use the TLF to generate a whole bunch of links in a stream that flowed through several columns within a composition. The first problem I noticed was that all of the links when chained together were converted to one link. I lost track of how this was fixed but I think it had to do with the links not having href attributes. Anyway, that wasn’t the main problem I encountered. I managed to stream my links together into one long chain and had them nicely justified to the columns :

Screen shot 2010-03-19 at 11.06.55
Uploaded with plasq‘s Skitch!

As you can see I was trying to create a nice wall of links. Eventually these will be names and the user will be able to click on a link for more information etc. Nice! So all it needs is a nice hover state on each link, right? So I add a hover format to the TextFlow and then fell off my chair and sobbed.

Check out the proof-of-concept I created here : http://lyraspace.com/transfer/tlf/TextFlow_POC.html and then read through the posts I wrote on the official forum :

http://forums.adobe.com/message/2682942#2682942
and
http://forums.adobe.com/thread/598789?tstart=0

As you can see, there’s a massive performance issue here with formatting links within a paragraph. It needs to re-draw the paragraph to format the link and with 500 links in the paragraph it’s going to get very sluggish.

The answer then is to create a paragraph for each link? No, this will create a new line for each link.

So I had to work out when a new link would sit on a new line and then create a new paragraph from that link.

And here’s how I did it …

programmatically creating a TextFlow stream of Link Elements

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <s:Application
  3.        
  4.         xmlns:fx="http://ns.adobe.com/mxml/2009"
  5.         xmlns:s="library://ns.adobe.com/flex/spark"
  6.         xmlns:mx="library://ns.adobe.com/flex/mx"
  7.         xmlns:maps="com.cravens.nr.maps.*"
  8.        
  9.         minWidth="955" minHeight="600"
  10.        
  11.         viewSourceURL="srcview/index.html"
  12.         >
  13.        
  14.         <fx:Declarations>
  15.                 <maps:MainEventMap id="eventMap" />
  16.                 <maps:ModelMap id="modelMap" />
  17.         </fx:Declarations>
  18.        
  19.         <fx:Script>
  20.                 <![CDATA[
  21.                         import com.cravens.nr.model.FontsManager;
  22.                        
  23.                         import flash.text.engine.BreakOpportunity;
  24.                         import flash.text.engine.FontLookup;
  25.                         import flash.text.engine.RenderingMode;
  26.                         import flash.text.engine.TypographicCase;
  27.                        
  28.                         import flashx.textLayout.compose.TextFlowLine;
  29.                         import flashx.textLayout.conversion.TextConverter;
  30.                         import flashx.textLayout.elements.Configuration;
  31.                         import flashx.textLayout.elements.LinkElement;
  32.                         import flashx.textLayout.elements.OverflowPolicy;
  33.                         import flashx.textLayout.elements.ParagraphElement;
  34.                         import flashx.textLayout.elements.SpanElement;
  35.                         import flashx.textLayout.elements.SubParagraphGroupElement;
  36.                         import flashx.textLayout.elements.TextFlow;
  37.                         import flashx.textLayout.events.CompositionCompleteEvent;
  38.                         import flashx.textLayout.events.DamageEvent;
  39.                         import flashx.textLayout.events.FlowOperationEvent;
  40.                         import flashx.textLayout.events.UpdateCompleteEvent;
  41.                         import flashx.textLayout.formats.TextAlign;
  42.                         import flashx.textLayout.formats.TextLayoutFormat;
  43.                        
  44.                         import mx.collections.ArrayCollection;
  45.                         import mx.events.FlexEvent;
  46.                         import mx.utils.ObjectUtil;
  47.                        
  48.                         protected var config:Configuration;
  49.                        
  50.                         protected var paragraphText:String = "<p>";
  51.                        
  52.                         [Bindable]
  53.                         protected var textFlow:TextFlow;
  54.                        
  55.                         [Bindable]
  56.                         protected var namesArr:ArrayCollection;
  57.                        
  58.                         protected var linkCount:int = 0;
  59.                        
  60.                         protected var para:ParagraphElement;
  61.                         protected var link:LinkElement;
  62.                         protected var space:SpanElement;
  63.                        
  64.                         protected var updateDelayTimer:Timer;
  65.                        
  66.                         //---------------------------------------------------------------------------
  67.                         public function handleResult(e:Object):void
  68.                         {
  69.                                 trace("Result:"+ObjectUtil.toString(e));
  70.                                
  71.                                 /*
  72.                                         The XML structure returned from the server maps to an ArrayCollection
  73.                                 */
  74.                                 namesArr = e.signatures.signature as ArrayCollection;
  75.                                 startTextFlowCreation();
  76.                         }
  77.                         //---------------------------------------------------------
  78.                         public function handleFault(e:Object):void
  79.                         {
  80.                                 trace("Fault:"+ObjectUtil.toString(e));
  81.                         }
  82.                         //---------------------------------------------------------------------------
  83.                         protected function startTextFlowCreation():void
  84.                         {
  85.                                 FontsManager.getInstance();
  86.                                
  87.                                 config = Configuration(TextFlow.defaultConfiguration).clone();
  88.                                
  89.                                 var linkNormalFormat:TextLayoutFormat = new TextLayoutFormat();
  90.                                 linkNormalFormat.color = 0x000000;
  91.                                 var linkHoverFormat:TextLayoutFormat = new TextLayoutFormat();
  92.                                 linkHoverFormat.color = 0x999999;
  93.                                
  94.                                 var defaultFormat:TextLayoutFormat = new TextLayoutFormat();
  95.                                 defaultFormat.paddingTop = 6;
  96.                                 defaultFormat.color = 0x333333;
  97.                                 defaultFormat.textAlign = TextAlign.JUSTIFY;
  98.                                 defaultFormat.textAlignLast = TextAlign.JUSTIFY;
  99.                                 defaultFormat.fontSize = 10;
  100.                                 defaultFormat.lineHeight = 12;
  101.                                 defaultFormat.fontLookup = FontLookup.EMBEDDED_CFF;
  102.                                 defaultFormat.typographicCase = TypographicCase.UPPERCASE;
  103.                                 defaultFormat.renderingMode = RenderingMode.CFF;
  104.                                 defaultFormat.fontFamily = FontsManager.NOVAMONO;
  105.                                
  106.                                 config.textFlowInitialFormat = defaultFormat;
  107.                                 config.defaultLinkNormalFormat = linkNormalFormat;
  108.                                 config.defaultLinkHoverFormat = linkHoverFormat;
  109.                                 config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
  110.                                
  111.                                 textFlow = new TextFlow(config);
  112.                                 textFlow.columnCount = 4;
  113.                                
  114.                                 para = new ParagraphElement();
  115.                                 textFlow.addChild(para);
  116.                                
  117.                                 updateDelayTimer = new Timer(50,1);
  118.                                 updateDelayTimer.addEventListener(TimerEvent.TIMER_COMPLETE,checkLink);
  119.                                 addLinkElement();
  120.                         }
  121.                         //---------------------------------------------------------
  122.                         protected function addLinkElement():void
  123.                         {
  124.                                 var o:Object = namesArr[linkCount];
  125.                                 var firstName:String = o.first_name;
  126.                                 var lastName:String = o.last_name;
  127.                                
  128.                                 link = new LinkElement();
  129.                                 link.href = "event:"+o.id;
  130.                                 var linkText:SpanElement = new SpanElement();
  131.                                 linkText.text = o.first_name+" "+o.last_name;
  132.                                 space = new SpanElement();
  133.                                 space.text = " ";
  134.                                 link.addChild(linkText);
  135.                                
  136.                                 para.addChild(link);
  137.                                 para.addChild(space);
  138.                                
  139.                                 textFlow.flowComposer.composeToPosition(textFlow.textLength);
  140.                                
  141.                                 updateDelayTimer.start();
  142.                         }
  143.                         //---------------------------------------------------------
  144.                         protected function checkLink(event:TimerEvent = null):void
  145.                         {
  146.                                 updateDelayTimer.reset();
  147.                                
  148.                                 if(link)
  149.                                 {
  150.                                         var linkStart:int = link.getAbsoluteStart();
  151.                                         var linkEnd:int = linkStart+link.textLength;
  152.                                        
  153.                                         var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
  154.                                         var lineStart:int = textFlowLine.absoluteStart;
  155.                                         var isStartOfLine:Boolean = linkStart==lineStart;
  156.                                        
  157.                                         if(isStartOfLine && linkStart!=0)
  158.                                         {
  159.                                                 para.removeChild(link);
  160.                                                 para.removeChild(space);
  161.                                                
  162.                                                 para = new ParagraphElement();
  163.                                                 textFlow.addChild(para);
  164.                                                
  165.                                                 para.addChild(link);
  166.                                                 para.addChild(space);
  167.                                         }
  168.                                        
  169.                                         linkCount++;
  170.                                         if(linkCount == namesArr.length) textFlowComplete();
  171.                                         else addLinkElement();
  172.                                 }
  173.                         }
  174.                         //---------------------------------------------------------
  175.                         protected function textFlowComplete():void
  176.                         {
  177.                                 updateDelayTimer.removeEventListener(TimerEvent.TIMER_COMPLETE,checkLink);
  178.                                 updateDelayTimer = null;
  179.                         }
  180.  
  181.                 ]]>
  182.         </fx:Script>
  183.        
  184.         <s:VGroup width="100%" height="100%">
  185.                 <s:TextArea
  186.                        
  187.                         id="textArea"
  188.                         width="100%"
  189.                         height="100%"
  190.                         textFlow="{textFlow}"
  191.                         editable="false"
  192.                         selectable="false"
  193.                         />
  194.                
  195.         </s:VGroup>
  196.        
  197.        
  198. </s:Application>

In the example above you have to assume that the names_arr ArrayCollection is loaded in via the Mate framework eventMap and that this collection is made up of objects with a firstName and lastName property that I can use to create the links.

The formatting of the TextFlow can be created in advance by setting up a Configuration object and adding it as an argument while instantiating the TextFlow.

Creating a TextFlow Configuration object

  1. config = Configuration(TextFlow.defaultConfiguration).clone();
  2.                                
  3.                                 var linkNormalFormat:TextLayoutFormat = new TextLayoutFormat();
  4.                                 linkNormalFormat.color = 0x000000;
  5.                                 var linkHoverFormat:TextLayoutFormat = new TextLayoutFormat();
  6.                                 linkHoverFormat.color = 0x999999;
  7.                                
  8.                                 var defaultFormat:TextLayoutFormat = new TextLayoutFormat();
  9.                                 defaultFormat.paddingTop = 6;
  10.                                 defaultFormat.color = 0x333333;
  11.                                 defaultFormat.textAlign = TextAlign.JUSTIFY;
  12.                                 defaultFormat.textAlignLast = TextAlign.JUSTIFY;
  13.                                 defaultFormat.fontSize = 10;
  14.                                 defaultFormat.lineHeight = 12;
  15.                                 defaultFormat.fontLookup = FontLookup.EMBEDDED_CFF;
  16.                                 defaultFormat.typographicCase = TypographicCase.UPPERCASE;
  17.                                 defaultFormat.renderingMode = RenderingMode.CFF;
  18.                                 defaultFormat.fontFamily = FontsManager.NOVAMONO;
  19.                                
  20.                                 config.textFlowInitialFormat = defaultFormat;
  21.                                 config.defaultLinkNormalFormat = linkNormalFormat;
  22.                                 config.defaultLinkHoverFormat = linkHoverFormat;
  23.                                 config.overflowPolicy = OverflowPolicy.FIT_DESCENDERS;
  24.                                
  25.                                 textFlow = new TextFlow(config);

… and then to find the position of the element on the line I used this code :

Finding the position of an element within a TextFlow

  1. var linkStart:int = link.getAbsoluteStart();
  2.                                         var linkEnd:int = linkStart+link.textLength;
  3.                                        
  4.                                         var textFlowLine:TextFlowLine = textFlow.flowComposer.findLineAtPosition(linkStart);
  5.                                         var lineStart:int = textFlowLine.absoluteStart;
  6.                                         var isStartOfLine:Boolean = linkStart==lineStart;

As you can see I re-parent the element to a new ParagraphElement object if the LinkElement is at the start of the line.

The Text Layout Framework is still new and there are bugs, this highlights just how erratic the performance can be with it at the moment so handle cautiously and be sure to post your problems on the forum. The guys at Adobe were very helpful and as with all problems like this, you end up learning a hell of a lot about the framework when solving them!

Hey, you might have noticed I’m now using Snipplr to embed my code. I hope it doesn’t disappear any time soon.

Logging Flash with Google Chrome Developer Tools console

Actionscript, Flex No Comments »

Google Chrome’s new Developer Tools panel is really great but now it’s even better now that I’ve managed to log directly into it from Actionscript.

And it’s all thanks to this blog post : http://aut0poietic.us/2008/10/22/using-the-flex-3-logging-framework

All I had to do was create the ExternalConsoleTarget class from his example and then grab the code for the Console Logging wrapper. Then in my project import the Console class and log like this …

Console.debug("Hello Chrome");

It worked for me straight out of the box. Wonderful.

And because I’m a bloody nice bloke here’s my test project which includes a SWC I made so you can use the two classes in your own projects: http://lyraspace.com/tools/ConsoleLogger/Main.html

Removing specific DisplayObjects from a container in Actionscript 3

Actionscript 3 Comments »

This is a common mistake when trying to remove certain DisplayObject objects from a container in Actionscript. I fell into it again today and kicked myself around the office for five minutes afterward.

If you want to remove specific DisplayObjects from a container you may be tempted to do something like this …

  1. for (i=0; i<container_mc.numChildren; i++)
  2.    {
  3.     if(container.getChildAt(i) == mySprite || container_mc.getChildAt(i) == anotherSprite) container_mc.removeChildAt(i);
  4.    }

This will work unless one of the Sprites is at the top of the stack in which case that object wont be removed. But WHY?!!!

Well because the numChildren property will reduce as the objects get removed so eventually it will stop before the last object. The solution is to compensate by adjusting the variable ‘i’ in the for loop. Like so …

  1. for (i=0; i<container_mc.numChildren; i++)
  2.    {
  3.     if(container.getChildAt(i) == mySprite || container_mc.getChildAt(i) == anotherSprite) { container_mc.removeChildAt(i); i–;}
  4.    }

Hope this helps someone and prevents any self-harm.

New touch/gesture events in Actionscript 3 for Flash Player 10.1

Actionscript, Flash, Flex 1 Comment »

This video from a MAX session has an informative demonstration of the new touch/gesture events in 10.1. Also loads of really cool new features in the AIR 2.0 runtime.

What’s coming in Adobe AIR 2.0

Here’s a great article outlining all of the new events, classes and information on how to manage the input.

I’ve scraped the code off this article for my own reference below. Please do read the article though.


flash.ui.Multitouch
[static] inputMode:String
[static] maxTouchPoints:int
[static] supportedGestures:Vector
[static] supportsGestureEvents:Boolean
[static] supportsTouchEvents:Boolean

Touch Input mode

flash.events.MultitouchInputMode
Multitouch.inputMode
MultitouchInputMode.TOUCH_POINT
MultitouchInputMode.NONE
MultitouchInputMode.GESTURE

Mouse

MouseEvent.CLICK
MouseEvent.DOUBLE_CLICK
MouseEvent.MOUSE_DOWN
MouseEvent.MOUSE_UP
MouseEvent.MOUSE_MOVE
MouseEvent.MOUSE_OUT
MouseEvent.MOUSE_OVER
MouseEvent.ROLL_OUT
MouseEvent.ROLL_OVER

Touch

TouchEvent.TOUCH_BEGIN
TouchEvent.TOUCH_MOVE
TouchEvent.TOUCH_END
TouchEvent.TOUCH_TAP
TouchEvent.TOUCH_OUT
TouchEvent.TOUCH_OVER
TouchEvent.TOUCH_ROLL_OUT
TouchEvent.TOUCH_ROLL_OVER
TouchEvent.TOUCH_TAP

Touch management

Many multi-touch developers like to use an Object or Array of points to keep track of touch inputs (aka blobs) as well as an ID, and X/Y coordinatees for each point. It looks like Flash Player 10.1 will help you conform to these conventions, ActionScript-style.


flash.utils.Dictionary /* Used to track Arrays of Points */
flash.geom.Point /* One contact point */
TouchEvent.touchPointID /* Unique ID for each touch contact point */
TouchEvent.stageX
TouchEvent.stageY
TouchEvent.isPrimaryTouchPoint

Gestures

flash.events.GestureEvent;
flash.events.GesturePhase;
flash.events.GesturePhaseEvent;
flash.events.TransformGestureEvent;

/* GestureEvent */
GestureEvent.GESTURE_TWO_FINGER_TAP
GestureEvent.phase: String
GestureEvent.localX:Number
GestureEvent.localY:Number
GestureEvent.stageX:Number
GestureEvent.stageY:Number

/* GesturePhase */
GesturePhase.BEGIN
GesturePhase.END
GesturePhase.UPDATE

/* GesturePhaseEvent */
GesturePhaseEvent.GESTURE_PAN
GesturePhaseEvent.GESTURE_PRESS_AND_TAP
GesturePhaseEvent.GESTURE_ROTATE
GesturePhaseEvent.GESTURE_ZOOM //Is the same thing as 'pinch'
GesturePhaseEvent.offsetX:Number
GesturePhaseEvent.offsetY:Number
GesturePhaseEvent.rotation:Number
GesturePhaseEvent.scaleX:Number
GesturePhaseEvent.scaleY:Number

Orientation recognition

flash.display.StageOrientation;
/* e.g. StageOrientation.DEFAULT */

flash.display.StageDisplayState;
/* e.g. stage.displayState = FULL_SCREEN_INTERACTIVE; */

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in