Pages

Sunday, January 9, 2011

DiagonalBlockWipe shader for Flex effects

I played around a bit with PixelBender shaders and the result is a nice little shader which can be used for transition effects.

I started out with Adobe's Wipe effect and created a diagonal block wipe, which looks like that:

To view this page ensure that Adobe Flash Player version 10.0.0 or greater is installed.

You can change both the direction of the wipe and the size of the blocks.
Now let's have a look at the code:

<languageVersion:1.0;>
kernel DiagonalBlockWipe<
 namespace: "flex";
 vendor: "Norbert Kopcsek";
 version: 1;
 description: "Diagonal block wipe between two images for Flex effects";
>
{
 // first input is unused in FxAnimateTransitionShader effect
 input image4 src0;
 input image4 from;
 input image4 to;
 output pixel4 dst;

 parameter float progress<
  minValue: 0.00;
  maxValue: 1.00;
  defaultValue: 0.0;
 >;
 
 parameter float blockSize<
  minValue: 1.0;
  maxValue: 256.0;
  defaultValue: 30.0;
 >;

 parameter float width<
  minValue: 0.0;
  maxValue: 1024.0;
  defaultValue: 576.0;
 >;

 parameter float height<
  minValue: 0.00;
  maxValue: 1024.0;
  defaultValue: 384.0;
 >;
 
 // 0 == top left->bottom right
 // 1 == top right->bottom left
 // 2 == bottom right->top left
 // 3 == bottom left->top right
 parameter int direction<
  minValue: 0;
  maxValue: 3;
  defaultValue: 0;
 >;

 void
 evaluatePixel()
 {
  float2 coord = outCoord();
  // Use src0 just to defeat the optimizer from removing the input
  pixel4 color1 = sampleNearest(src0, coord);
  color1 = sampleNearest(from, coord);
  pixel4 color2 = sampleNearest(to, coord);
  
  float2 projectedCoord;
  
  if (direction == 0) {
   projectedCoord = coord;
  }
  else if (direction == 1) {
   projectedCoord = float2( width - coord.x, coord.y );
  }
  else if (direction == 2) {
   projectedCoord = float2( width - coord.x, height - coord.y );
  }
  else {
   projectedCoord = float2( coord.x, height - coord.y );
  }
  
  float blockProgress = floor( ( width + height ) * progress / blockSize );
  float2 block = floor( projectedCoord / float2( blockSize, blockSize ));
  dst = (block.x > blockProgress - block.y && block.y > blockProgress - block.x ) ? color1 : color2;
  
  // workaround for Flash filter bug that replicates last column/row
  if (coord.x >= width || coord.y >= height) {
   dst.a = 0.0;
  }
 }
}

The major part of the shader is the same as in Adobe's Wipe effect. The only parameter I added is blockSize, which defines the length of one side of the blocks used in the transition. The change of the code is rather simple: First it calculates the block it is in and then decides whether the block shows color1 of the from image or color2 of the to image based on the given blockProgress.

Monday, December 6, 2010

Ant macros for Flex, Part 2

While the first part was an introduction to Ant macros, this second part will show you some more macros for Flex/Actionscript projects.

The first one uses the compc task to compile all classes of a given source directory to a swc library:

<macrodef name="compile-lib">
  <attribute name="source-dir" default="${source.dir}"/>
  <attribute name="target-dir" default="${build.dir}"/>
  <attribute name="swc-name"/>
  <element name="args" optional="true"/>

  <sequential>
    <fileset id="sources" dir="@{source-dir}">
      <include name="**/*.as"/>
      <include name="**/*.mxml"/>
    </fileset>
    <pathconvert property="classes" pathsep=" " refid="sources">
      <compositemapper>
        <chainedmapper>
          <globmapper from="@{source-dir}/*" to="*" handledirsep="true" />
          <mapper type="package" from="*.as" to="*" />
        </chainedmapper>
        <chainedmapper>
          <globmapper from="@{source-dir}/*" to="*" handledirsep="true" />
          <mapper type="package" from="*.mxml" to="*" />
        </chainedmapper>
      </compositemapper>
    </pathconvert>
    
    <echo message="Compiling library @{swc-name} including classes: ${classes}"/>
    <compc
      output="@{target-dir}/@{swc-name}.swc"
      include-classes="${classes}"
      link-report="@{target-dir}/@{swc-name}-link-report.xml"
      keep-generated-actionscript="true"
      optimize="true"
      incremental="true">
        
      <source-path path-element="@{source-dir}"/>
      <external-library-path file="${libs.dir}/*.swc" append="false"/>
      <external-library-path file="${FLEX_HOME}/frameworks/libs/*.swc" append="false"/>
      <external-library-path file="${flex.playerglobal}" append="false"/>
      <args/>
    </compc>

  </sequential>
</macrodef>

In a first step the macro puts all *.as and *.mxml files in the source-dir into a fileset and then converts them to a comma separated list containing all qualified class names using pathconvert (for more on this see here and here and for an alternative approach see here).

After that it compiles the swc library using the compc task. Again you can pass additional compiler arguments using the args element (see part 1).

The second macro takes a swc library and creates an optimized runtime shared library out of it:

<macrodef name="create-rsl">
  <attribute name="source-dir" default="${build.dir}"/>
  <attribute name="target-dir" default="${build.dir}"/>
  <attribute name="swc-name"/>
  <sequential>
    <unzip src="@{source-dir}/@{swc-name}.swc" dest="@{target-dir}" overwrite="true">
      <patternset>
        <include name="library.swf"/>
      </patternset>
    </unzip>
    <move file="@{target-dir}/library.swf" tofile="@{target-dir}/@{swc-name}-library.swf"/>
    <exec executable="${flex.optimizer}" failonerror="true">
      <arg value="-input=@{target-dir}/@{swc-name}-library.swf"/>
      <arg value="-output=@{target-dir}/@{swc-name}.swf"/>
    </exec>
    <delete file="@{target-dir}/@{swc-name}-library.swf"/>
  </sequential>
</macrodef>

To do so, it first uses the unzip task to extract the library.swf, renames it using the move and then calls the flex optimizer using the exec task. The optimizer strips the swf file of all debugging code and metadata. With those two macros you can create runtime shared libraries with just two calls:

<target name="build-mylibrary">
  <compile-lib swc-name="MyLibrary" source-dir="${basedir}/src-mylibrary"/>
  <create-rsl swc-name="MyLibrary"/>
</target>

You can have macros for all kinds of things, be it running the flex unit test, building asdoc, running pmd or compiling locales. And once written you can move them from project to project.

More information on macros for Flex/Actionscript can be found here.

Sunday, December 5, 2010

Ant macros for Flex, Part 1

Ant macros are a nice way to reduce the size of build scripts, especially when you have lot's of modules and runtime shared libraries. The macrodef task always contains a sequential task containing all the actions to be done and multiple attributes and elements.

The first example is a macro for compiling modules using the mxmlc task (for applications you would not want use external libraries):

<macrodef name="compile-module">
  <attribute name="source-dir" default="${source.dir}"/>
  <attribute name="build-dir" default="${build.dir}"/>
  <attribute name="module-name"/>
  <element name="args" optional="true"/>
  <sequential>
    <echo message="Compiling module @{module-name}"/>
    <mxmlc debug="true" file="@{source-dir}/@{module-name}.mxml" incremental="true" link-report="@{build-dir}/@{module-name}-link-report.xml" locale="en_US" optimize="true" output="@{build-dir}/@{module-name}.swf">
      <source-path path-element="@{source-dir}"/>
      <external-library-path append="false" file="${libs.dir}/*.swc"/>
      <external-library-path append="false" file="${FLEX_HOME}/frameworks/locale/{locale}"/>
      <external-library-path append="false" file="${FLEX_HOME}/frameworks/libs/*.swc"/>
      <external-library-path file="${flex.playerglobal}"/>
      <args/>
    </mxmlc>
  </sequential>
</macrodef>

As you can see, the macro has three attributes, source-dir, build-dir and module-name as which get substituted in the sequential task. Additionally there is a args element which is also referenced in sequential task.

The following example shows how to use the macro:

<target name="compile-mymodule">
  <compile-module module-name="MyModule" source-dir="${source.dir}-mymodule">
    <args>
      <external-library-path file="${build.dir}/Core.swc" append="false"/>
    </args>
  </compile-module>
</target>

The macro compile-module is called with the attributes module-name and source-dir. All attributes with a default value can be left out, like build-dir. The args element is used to pass additional compiler arguments, like an external-library-path.

Macros are very useful for standardizing the build process. If you want to create link reports for all your modules and transform them using xslt for better readability, you can do all this in the macro definition.

The second part will cover macros for compiling libraries and creating runtime shared libraries.

Friday, December 3, 2010

Localization of Flex applications using ExcelAntTasks

ExcelAntTasks is a small tool which allows you to define all locales in an Excel file and to convert them automatically to *.properties files using Ant.

The following image shows locales defined in an Excel file:



The first column contains the keys and the second row contains the identifiers for the locales (en_US, de_DE and so on). Empty fields (see de_DE) mean that the value of a fallback locale (en_US) should be used. All blank fields won't show up in the *.properties files.

Now let's take a look at the build script to convert the locales:

<project default="buildLocales" name="ExcelAntTask Test">
  <taskdef classname="excelanttasks.ConvertExcel" classpath="build/excelAntTasks.jar" name="convert"/>
  <target name="buildLocales">
    <convert excelpath="Locale.xls" localepath="locale/"/>
  </target>
</project>

The ExcelAntTasks are made available using the taskdef tag. After that you can convert Excel files using the convert tag, which provides the following attributes:

- excelpath: The path to the excel file. Default is "Locale.xls".
- localepath: The path where to store the created property files. Default is "locale/".
- resourcename: The name of the created property file. Default is "Locale.properties".
- headerrownumber: The number of the header row. The header row contains the identifiers for the locales (en_US, de_DE and so on). Default is 2.
- keycolumnnumber: The number of the key column. The key column contains the keys used in Flex. Default is 1.
- keyLabel: The identifier of the key column. Default is "labelkey".

Running the build script creates a folder and a *.properties file for each locale identifier defined in the Excel file:



As you can see, there's one additional locales file called debug which maps the keys to the keys and which can be used to identify not yet localized string in the user interface.

Right now it is not possible to convert XML-based Excel files (XLSX, Excel 2007+), but this feature is definitely planned.

Tuesday, November 30, 2010

Alternativ context menu for Flash, part 2

The first part showed you how to fetch the right click event and how to forward it to the Flash player. The events were process on the application level, which is not very useful for complex applications. Instead you want your sub components to process the right click event.

Therefore the following steps need to be done:
  • Find the component under the mouse cursor
  • Dispatch a right click event on this this component 
  • Listen to right click events dispatch from a component or its sub components (event bubbling)

The first step can be accomplished using the following code:

private function findComponentBelowCursor():UIComponent
{
  var globalPoint:Point = new Point( FlexGlobals.topLevelApplication.mouseX, FlexGlobals.topLevelApplication.mouseY );
  var objects:Array = FlexGlobals.topLevelApplication.getObjectsUnderPoint( globalPoint );
   
  for ( var i:int = objects.length - 1; i >= 0; i-- )
  {
    var o:Object = objects[ i ];
    if( o is UIComponent )
    {
      return o as UIComponent;
    }
    if( o.hasOwnProperty( "parent" ) && o.parent is UIComponent )
    {
      return o.parent as UIComponent;
    }
    if( o.hasOwnProperty( "document" ) && o.document is UIComponent )
    {
      return o.document as UIComponent;
    }
  }
  return null;
}

The function getObjectsUnderPoint returns all  DisplayObjects under the given point (globalPoint), which are searched for the topmost UIComponent.

If a UIComponent was found, a bubbling event is dispatched:

private function dispatchRightClickEvent( component:UIComponent ):void
{
  var globalPoint:Point = new Point( FlexGlobals.topLevelApplication.mouseX, FlexGlobals.topLevelApplication.mouseY );
  var localPoint:Point = object.globalToLocal( globalPoint );
  object.dispatchEvent( new RightClickEvent( RightClickEvent.RIGHT_CLICK, true, true, localPoint.x, localPoint.y, object ));
}


Note that a custom RightClickEvent extending the MouseEvent was used in the sample code. Also it is very important to set bubbling to true so that parent components can listen to the event as well.

Here is a small sample application:

To view this page ensure that Adobe Flash Player version 10.0.0 or greater is installed.



If you right click into one of the lists, the RightClickEvent is dispatched on DefaultItemRenderer, bubbles up to the list which has event listener registered and is processed in the event handler.

It is also very important to properly clean up the menu every time you click outside of the menu or open another one. For this you can use a static manager class like the following:

package
{
  import mx.controls.Menu;
  import mx.core.FlexGlobals;
  import mx.managers.PopUpManager;

  public class RightClickMenuManager
  {
    private static var menu:Menu;
  
    public static function showMenu( menu:Menu ):void
    {
      hideMenu();
      menu.show( FlexGlobals.topLevelApplication.mouseX, FlexGlobals.topLevelApplication.mouseY );
      RightClickMenuManager.menu = menu;
    }
  
    public static function hideMenu():void
    {
      if ( menu != null )
      {
        PopUpManager.removePopUp( menu );
      }
    }
  }
}

Monday, November 29, 2010

Alternativ context menu for Flash, part 1

The standard context menu of Flash imposes a few limitations which make it difficult to build a complex user interface with good usability. There are the following limitations:
  • standard menu items like "About Flash Player" cannot be removed
  • you can have 15 menu items at max
  • you can not have sub menus
  • several key words are reserved, like "copy" und "cut" and can not be used

Some clever developers have found a way to catch the right click event in the HTML container using Javascript and to forward the event to the Flash Player via the External interface (see here for the project site).

A right click in the following sample application opens a simple Flex menu which does not have the limitations descriped above.

To view this page ensure that Adobe Flash Player version 10.0.0 or greater is installed.




Now let's have a look at the code:

import mx.controls.Menu;
import mx.events.FlexMouseEvent;
import mx.managers.PopUpManager;

private var menu:Menu;

private function creationCompleteHandler( event:Event ):void
{
  ExternalInterface.addCallback( "rightClick", rightClickHandler );
}
private function rightClickHandler():void
{
  showMenu();
}
public function showMenu():void
{
  hideMenu();
  menu = Menu.createMenu( this, menuItems );
  menu.show( stage.mouseX, stage.mouseY );
}
public function hideMenu():void
{
  if( menu != null )
  {
    PopUpManager.removePopUp( menu );
  }
}

In the creationCompleteHandler  a callback is added to external interface which gets called every time the right mouse button is pressed.The callback rightClickHandler simply opens the Flex menu.

Check out the following links for more information on the topic: