Playing with Feathers UI Components for Starling Framework

Posted on October 9, 2012 | 9 comments

The Starling Framework allows developers to build 2D hardware accelerated (Stage3D API) games with an API similar to the flash.display.* classes. This framework is used by Angry Birds on Facebook. The Starling community is quite vibrant and many extensions are being made for it. One of these extensions that has grown into a full project by it self is called Feathers UI (previously known as Foxhole UI components), developed by Josh Tynjala.

The feathersui.com website provides great documentation, api docs, and examples of how to get started with the Feathers UI components.

For myself there is no better way to learn something new than using it in a project. I wanted to build out a basic file inspector application and use it to play with the Feathers UI components. It took a bit to figure out what I wanted but the Feathers UI components should feel pretty close to non-mxml Flex or QNX UI. Feathers controls have a defined lifecycle and an approach to theme your components globally. The example apps and wiki documentation is a great place to start putting the pieces together.

Once I was a bit comfortable with the basics I started addressing what I wanted in my application. It quickly turned to wanting percent based width and height for laying out a group of controls. Basically a VGroup or HGroup in Flex. The Feathers ScrollContainer and other controls do support a layout property that handles the layout of their children. So I was torn between trying to extend ScrollContainer or just make a control of my own. I went with building my own and worked hard to use the Feathers UI lifecycle (initialize, draw, invalidate, etc…). This way I didn’t have to worry about breaking ScrollContainer logic and focus on what I wanted the control to do while creating my first custom Feathers UI component.

I wanted a VGroup type component, but with the ability to set certain children with percentage based height. I didn’t want to add percentWidth and percentHeight properties to FeathersControl, which all components inherit. This would allow others to try my custom control without a custom Feathers UI fork. I also didn’t want to force adherence to the layout property API because of my limited time to do a prototype, so all the layout is inherited in the VGroup. I did end up extending out a Group class and reusing parts for an HGroup as well, but the vertical and horizontal layout differences are in the VGroup and HGroup draw() function calls respectively.

Let us look at some code, you can find all custom Feathers UI components on my Github in the RenaunsCoreLib repo:

com.renaun.controls.Group

package com.renaun.controls
{
import feathers.controls.Label;
import feathers.core.FeathersControl;

import starling.display.DisplayObject;

public class Group extends FeathersControl
{
    public function Group()
    {
        super();
   
    }
   
    protected var children:Vector.<FeathersControl> = new Vector.<FeathersControl>();
    protected var childrenPercentValues:Vector.<int> = new Vector.<int>();

    protected var explicitTotal:Number;

    protected var percentTotalValue:Number;
   
    protected var isValid:Boolean = false;

    protected var isFirstTime:Boolean = false;

    /**
     *  Currently you have to add items in order, there is not addLayoutItemAt.
     *
     *  @param item The target item you want to add to this group, add in order
     *      @param percentHeight The percent value of the VGroup's height. 100 = 100%, and if the total percent is > 100 it will be scaled by the value/totalPrecents.
     */

    public function addLayoutItem(item:FeathersControl, percentValue:int = -1):void
    {
        addChild(item);
        children.push(item);
        item.onResize.add(resizeHandler);
        childrenPercentValues.push(percentValue);
        measure();
        invalidate();
    }
   
    override protected function initialize():void
    {
        //trace("init");
        super.initialize();
        isFirstTime = true;
    }
   
    protected function measure():void
    {
    }
   
    protected function resizeHandler(target:FeathersControl, oldWidth:Number, oldHeight:Number):void
    {
        /*
        if (target is Label)
            trace("resizeHandler2["+(target as Label).text+"]: " + oldWidth + " - " + width);
        else
        trace("resizeHandler2["+target+"]: " + oldWidth + " - " + width);
        */

        isValid = false;
        invalidate();
    }
   
}
}

In the base Group class you see the addLayoutItem function which takes the display object reference and a percent value as parameters. It assumes you are going to add the display objects in order and define which ones are to have percentage based values and which ones are not. If you do not give it a percent value it assumes it has its height already explicitly set. If the total percent value is greater then 100, then I just divy up the actual heights as: percent value / total percent.

Note: It is not fully flushed out API but more of an experiment, for example I would add an addLayoutItemAt function and maybe other ways to tweak values after they are added.

com.renaun.controls.VGroup

package com.renaun.controls
{
import feathers.core.FeathersControl;

import starling.display.DisplayObject;

public class VGroup extends Group
{
    public function VGroup()
    {
        super();
   
    }

    public var gap:int = 8;
    public var paddingLeft:int = 8;
    public var paddingRight:int = 8;
    public var paddingTop:int = 8;
    public var paddingBottom:int = 8;
   
    override protected function measure():void
    {
        var item:DisplayObject;
        explicitTotal = -gap;
        percentTotalValue = 0;
        var i:int;
        for (i = 0; i < children.length; i++)
        {
            item = children[i];
            if (childrenPercentValues[i] > 0)
                percentTotalValue += childrenPercentValues[i];
            else
            {
           
                if (item is FeathersControl)
                {
                    //item.height = (item as BitmapFontTextRenderer).measureText().y;
                    (item as FeathersControl).validate();
                }
                explicitTotal = explicitTotal + item.height;
            }      
            explicitTotal += gap;
        }
        isValid = true;
    }
   
    override protected function draw():void
    {
        // Delay items width/height are still not valid inside initalize method so have to wait for draw
        if (isFirstTime)
        {
            measure();
            isFirstTime = false;
        }
        var availableHeightForPercents:Number = explicitHeight - explicitTotal - paddingTop - paddingBottom;
        //trace("availableHeightForPercents: " + availableHeightForPercents + " - " + explicitTotal + " - " + explicitHeight);
        // Make smaller if the percents add up more then 100
        if (percentTotalValue > 100)
            availableHeightForPercents = availableHeightForPercents / (percentTotalValue / 100);
       
        //trace("percentTotalValue: " + percentTotalValue + " aHFP: " + availableHeightForPercents + " - " + explicitTotal + " - " + explicitHeight);
        var percentHeightValue:int = 0;
        var lastHeight:int = 0;
        var i:int;
        var item:DisplayObject;
        for (i = 0; i < children.length; i++)
        {
            item = children[i];
            // Set Y
            item.y = lastHeight + paddingTop;
            if (childrenPercentValues[i] > 0)
            {
                percentHeightValue = childrenPercentValues[i];
                // Set HEIGHT
                item.height = int(availableHeightForPercents * percentHeightValue / 100);
            }
            lastHeight += item.height + gap;
           
            // Set X
            item.x = paddingLeft;
            // Set WIDTH
            item.width = explicitWidth - paddingLeft - paddingRight;
            //trace("lastHeight : " + lastHeight);
        }
    }
}
}

In the actual layout code implemented inside the draw() function I proceed first to get the total explicit height values of non-percent based layout items that were added. Then I take the remainder height and divy it up between the percent based items. Fairly straightforward and quite easy to create my own custom Feathers UI control.

Using the VGroup looks like this:

var vgroup:VGroup = new VGroup();

vgroup.addLayoutItem(renderer);
vgroup.addLayoutItem(list, 100);
vgroup.addLayoutItem(appCountLabel);

addChild(vgroup);

My components are not vetted for all bugs, API completeness, or performance issues as this was just my way getting to understand the Feathers UI components better. I did enjoy using the UI components and did not find it hard to extend and play with. I do need to thank Josh for answering my questions; he does a great job responding to the Feathers UI forums.

You can also check out the relative position manager class I made for fun. It supports left, right, top, bottom, horizontalCenter and verticalCenter.

  • http://twitter.com/JosephLabrecque Joseph Labrecque

    Great post! Lots of good info on how to do accelerated UI for an app without needing Flex.

  • http://www.justpinegames.com/ Tomislav Podhraški

    I’ve done something similar, but it’s implemented as a Layout, rather than a subclass of the component.

    It’s a part of bigger libarary we’re going to put on github, but it is not ready yet. However I’ve shared this part in the past:
    https://gist.github.com/3717680

    Here is an example how to use it:
    var layout:AutosizeLayout = new AutosizeLayout();
    layout.setComponentAutosizeSettings(container1, new AutosizeSettings(true, 30)); // 30%
    layout.setComponentAutosizeSettings(container2, new AutosizeSettings(true, 70)); // 70%

    // container1/container2 should be children of container you set the layout to.

    In similar fashion, we have a constraint based layout, which is useful if you want to stick something in one corner and have it stay there when resizing. On gist as well:
    https://gist.github.com/3791149

    Unfortunately, linked source is still using foxhole.

    • renaun

      Cool, definitely get it up on github soon :)

  • Nick Collins

    I would be cool if these containers were to implement [DefaultProperty] so that you could use FeathersUI with non-Flex MXML, similarly to what Ryan Campbell did with MinimalComps a while back. I’ll have to experiment with that… when I have some free time.

  • http://twitter.com/TeotiGraphix Michael Schmalle

    Hah, I did the same thing getting into it, look in the forum for a post
    called LayoutContainer. I felt the same way about ScrollContainer since I
    wanted to implement a simple TitleLayout.

    Except I mad a ILayout
    class. The point was I asked him if there was just a bare bones layout
    container like you just made with Group.

    Mike

  • khaled

    useful info thank you

  • http://www.facebook.com/guinetik Gui de Guinetik

    Amazing stuff. Just had the same idea “need to use feathers on my new project” and the first thing i thought was “i need a group component” :D thanks man

  • Ramez

    Really cool – we were missing these features to make more enterprise-like apps (MadComponents had percent-based groups from the start).

    Do you have an example on how to use the relative position manager? Thanks