AS3 Layout Manager BaseUI v4 finally released

| 7 min read

Finally, BaseUI version 4 is released!

If you don't know what is BaseUI, it is a lightweight layout manager written in pure AS3 that is helping you to handle the position and size of your elements in a Flash layout (a site, an application and or whatever you can build with AS3).

More specifically, it is calculating positions and size when you resize your browser to update your elements. BaseUI makes you able to use layout properties (such as top, bottom, horizontalCenter, and so on) on any DisplayObject instance, much like you would do in a Flex environment.

You can use BaseUI with a single DisplayOBject instance, with tons of DisplayObject instances or in a more complex system with layout classes (such as canvas, horizontal and vertical box, and tile).

Before showing you how it works with some simple examples, I'd like to mention that BaseUI has been completely rebuilt from scratch. I did that because I wasn't happy at all with a lot of things in the previous version. I wanted a very clean and effective library, with no hacks to make things works, which would make it very reliable.

The library is now contained in a com.soma.ui package, it will be part of the next Soma MVC Framework version.

I removed two things from the previous version:

  • the width and height ElementUI properties, as it wasn't making sense, you can now just set the width and height on your own DisplayObject instances.
  • the percentage values for width and height, that is not possible anymore, I might add specific properties in the future if requested

demo
source

Concept and the need of a reference

The important concept to understand is that you need to specify a reference to a BaseUI instance, or to the class that will handle the DisplayObject (which is the ElementUI class). A reference is what will be used to calculate size and position. For example, if you want your DisplayObject to be at 10 pixels from the bottom, you need to tell the library from what object. That's what a reference is.

The reference can be any DisplayObjectContainer, such as the stage, a sprite, a layout, etc. Once you understand completely the need of having a reference, it is good to know that is doesn't have to be a parent of your DisplayObject, it can be anything, anywhere. The only important things is that the reference must have a proper width and height, the stage or a layout class will always have it, but an empty Sprite instance won't, the values will be 0.

All the following example will use the stage as a reference, but again, you can use anything you want.

Create a BaseUI instance

var baseUI:BaseUI = new BaseUI(stage);

Adding and removing DisplayObject instances

var baseUI:BaseUI = new BaseUI(stage); var mySprite:Sprite = new Sprite(); var element:ElementUI = baseUI.add(mySprite); element.refresh(); addChild(mySprite);
baseUI.remove(mySprite); baseUI.removeAll();

Getting ElementUI instances

var element:ElementUI = baseUI.getElement(mySprite); var arrayOfElements:Array = baseUI.getElements(); // note that this is a copy of the real array var dictionaryOfElements:Dictionary = baseUI.getElementsAsDictionary(); // note that this is a copy of the real dictionary

Setting ElementUI properties

var element:ElementUI = baseUI.getElement(mySprite); element.bottom = 10; element.right = 10; element.refresh();
var element:ElementUI = baseUI.getElement(mySprite); element.horizontalCenter = 0; element.verticalCenter = 0; element.refresh();
var element:ElementUI = baseUI.getElement(mySprite); element.ratio = ElementUI.RATIO\_IN; element.refresh();

Reset ElementUI properties

You can reset the Number properties of the ElementUI instance by using NaN or null for the others:

element.top = 10; element.top = NaN;

element.ratio = ElementUI.RATIO\_IN; element.ratio = null; // or Element.RATIO\_NONE

element.rect = new Rectangle(0, 0, 50, 50); element.rect = null;

Or reset all of them:

element.reset();

Using ratio

The ratio is a kind of mode. You can use with a DisplayObject instance that keeps its proportion (Aspect Ratio) when it gets resized. Especially useful with pictures or backgrounds for example.

There is two ratio mode, a mode "in" and a mode "out".

The mode "in" will always display all the surface of the DisplayObject instance in its reference, you might get empty area if the reference has a different ratio. The mode "out" will always cover the surface of the reference, you might miss some part of the DisplayObject if the reference has a different ratio.

ElementUI aspect ratio

var element:ElementUI = baseUI.add(mySprite); element.ratio = ElementUI.RATIO\_IN; element.alignX = ElementUI.ALIGN\_RIGHT; element.alignY = ElementUI.ALIGN\_BOTTOM;

DisplayObject boundaries (optional)

When you use some ElementUI properties (such as right, bottom, horizontalCenter, etc), BaseUI will use the width and height of your DisplayObject instance to make its position calculation. For example, if you want the object to be at 10 pixels from the bottom, BaseUI needs to use the height.

This might prove difficult in some cases, for example if you have a specific "center point" or if the size of the DisplayObject instance changes all the time (if it is animated).

To solve this kind of problem, you can specify the boundaries of your DisplayObject to be used as a size in BaseUI instead of its real size. You can do that by using the "rect" and a Rectangle instance.

For example, if you draw a square in a sprite width 100 and height 100, but starting at x -50 and y -50 so it is centered. You can specify the boundaries like this:

var element:ElementUI = baseUI.add(myCenteredSprite); element.rect = new Rectangle(-50, -50, 100, 100);

And you can remove it this way:

element.rect = null;

ElementUI Listeners

An ElementUI instance can dispatch three events, in this order:

1. EventUI.WILL_CALCULATE (dispatched before any calculation) 2. EventUI.WILL_UPDATE (dispatched after calculation and before updating the DisplayObject) 3. EventUI.UPDATED (dispatched after that the DisplayObject has been updated)

You can stop the ElementUI process in the EventUI.WILL_CALCULATE and EventUI.WILL_UPDATE handlers using "event.preventDefault()".

var baseUI:BaseUI = new BaseUI(stage); var element:ElementUI = baseUI.add(mySprite); element.addEventListener(EventUI.WILL\_CALCULATE, willCalculateHandler); element.addEventListener(EventUI.WILL\_UPDATE, willUpdateHandler); element.addEventListener(EventUI.UPDATED, updatedHandler); element.right = 10; element.bottom = 10; element.refresh(); addChild(mySprite);

private function willCalculateHandler(event:EventUI):void { //event.preventDefault(); // stop the process before the calculation trace(event.element); // trace the ElementUI instance trace(event.element.object); // trace the DisplayObject instance trace(event.element.baseUI); // trace the BaseUI instance }

private function willUpdateHandler(event:EventUI):void { //event.preventDefault(); // stop the process before new properties are applied to the DisplayObject trace(event.element); // trace the ElementUI instance trace(event.element.object); // trace the DisplayObject instance trace(event.element.baseUI); // trace the BaseUI instance trace(event.properties); // trace the properties that will be applied to the DisplayObject }

private function updatedHandler(event:EventUI):void { trace(event.element); // trace the ElementUI instance trace(event.element.object); // trace the DisplayObject instance trace(event.element.baseUI); // trace the BaseUI instance trace(event.properties); // trace the properties that have been applied to the DisplayObject }

Refreshing an element

When you create an element, the position and size will be calculated when an Event.RESIZE occurs, which might not happen unless you resize your browser. For a performance matter, the calculation is not done when you change a property. To make the calculation and update your element, you might need to use the refresh method of your ElementUI instance (or BaseUI instance for all the ElementUI).

element.refresh(); baseUI.refresh();

Disposing and garbage collection

Both BaseUI and ElementUI classes have a dispose method to destroy variables and make the instance eligible for the garbage collection. The only one you have to care about is the BaseUI one, as the ElementUI will be properly destroyed when you use the remove and removeAll BaseUI methods.

baseUI.dispose(); baseUI = null;

Using layouts

Five layouts classes are built in BaseUI, all of them have ElementUI properties directly accessible.

- The LayoutUI class: the super class of all the other layouts classes, only a container, children stay un-touched - The CanvasUI class: handle the children with ElementUI properties - The HBoxUI class: display the children, one after the other, in a horizontal direction - The VBoxUI class: display the children, one after the other, in a vertical direction - The TileUI class: display the children, one after the other, and is in both direction and in a "multiline" way

All the layouts classes will have some common properties, such as background color, background transparency, rounded values, and if the content that goes outside of the boundaries of the layouts should be displayed or hidden.

You can also use layouts in other layouts, like adding canvas in tiles that are added in vertical boxes, etc. Be aware that we're still using flash here, and even if the library is efficient, it might get slower if you add thousands of tiles in thousands of other tiles with thousands of bitmaps. Calculation are made and everything has limits, especially Flash. Beside that, I got good performance with the library, you will be fine unless you do crazy things :)

The LayoutUI class

The LayoutUI class extends the MovieClip class and is nothing else that a container that will always have a size (width and height), even if there's nothing inside. Another difference with another built-in flash container (like Sprite, MovieClip, etc), is that the width and height will always be the one of the container regardless of what it contains.

For example, a layout instance (width 100 and height 100), that contains another DisplayObject (width 200 and height 200). The width and height values returned by a normal Sprite or MovieClip would be 200, while the layouts classes will return 100 (see getRealWidth and getRealHeight methods).

And that is true for all the layouts in the library, it is something to be aware of.

var layout:LayoutUI = new LayoutUI(stage, 400, 300);
layout.backgroundColor = 0xFF0000;
layout.backgroundAlpha = 0.2;
layout.bottom = 10;
layout.right = 10;
layout.refresh();
addChild(layout);

The CanvasUI class

The behavior of the CanvasUI class is very close to its super class (LayoutUI), with the different that an ElementUI instance is created (or removed) when you add children to its display list. Makes you able to handle the children of the CanvasUI instance with ElementUI properties.

var canvas:CanvasUI = new CanvasUI(stage, 400, 300);
canvas.backgroundColor = 0xFF0000;
canvas.backgroundAlpha = 0.2;
canvas.ratio = ElementUI.RATIO_IN;
addChild(canvas);

var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xFF0000, .5);
sprite.graphics.drawRect(0, 0, 100, 100);

var element:ElementUI = canvas.add(sprite);
element.right = 10;
element.bottom = 10;

canvas.refresh();

The HBoxUI class (horizontal box)

The goal of the HBoxUI class is that it will take from the positioning of its children, and align them in an horizontal way and only one row. Properties such as children gap, children align and children padding are introduced.

var hbox:HBoxUI = new HBoxUI(stage, 400, 300);
hbox.backgroundColor = 0xFF0000;
hbox.backgroundAlpha = 0.2;
hbox.ratio = ElementUI.RATIO_IN;
hbox.childrenGap = new GapUI(5, 5);
hbox.childrenPadding = new PaddingUI(5, 5, 5, 5);
hbox.childrenAlign = HBoxUI.ALIGN_BOTTOM_RIGHT;
addChild(hbox);

for (var i:int=0; i<8; ++i) {
var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xFF0000, .5);
sprite.graphics.drawRect(0, 0, 100, 100);
hbox.addChild(sprite);
}

hbox.refresh();

The VBoxUI class (vertical box)

The VBoxUI class is much like the HBoxUI one, but you found out, in a vertical way.

var vbox:VBoxUI = new VBoxUI(stage, 400, 300);
vbox.backgroundColor = 0xFF0000;
vbox.backgroundAlpha = 0.2;
vbox.ratio = ElementUI.RATIO_IN;
vbox.childrenGap = new GapUI(5, 5);
vbox.childrenPadding = new PaddingUI(5, 5, 5, 5);
vbox.childrenAlign = VBoxUI.ALIGN_BOTTOM_RIGHT;
addChild(vbox);

for (var i:int=0; i<8; ++i) {
var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xFF0000, .5);
sprite.graphics.drawRect(0, 0, 100, 100);
vbox.addChild(sprite);
}

vbox.refresh();

The TileUI class

A kind of mix between the HBoxUI and VBoxUI classes, Flex developer are used to it as well. It will show the children in multiple lines, in any direction depending of the setting used.

var tile:TileUI = new TileUI(stage, 400, 300);
tile.backgroundColor = 0xFF0000;
tile.backgroundAlpha = 0.2;
tile.ratio = ElementUI.RATIO_IN;
tile.childrenGap = new GapUI(5, 5);
tile.childrenPadding = new PaddingUI(5, 5, 5, 5);
tile.childrenAlign = TileUI.ALIGN_BOTTOM_RIGHT;
tile.childrenDirection = TileUI.DIRECTION_HORIZONTAL;
addChild(tile);

for (var i:int=0; i<16; ++i) {
var sprite:Sprite = new Sprite();
sprite.graphics.beginFill(0xFF0000, .5);
sprite.graphics.drawRect(0, 0, 100, 100);
tile.addChild(sprite);
}

tile.refresh();

I'm sure you will enjoy the library, it is very easy to use. Please send me some feedback :)

Happy development!