ActionScript 3.0 UI Elements

Hello people. Fear not! makemachine lives!

After a short break to work on some client projects, I’ve re-immersed myself in audio programming with ActionScript. As a part of the re-entry, I’ve written a lightweight user interface framework for rapid prototyping. In this article I’ll walk through how the components are built and how to use them in your own code. I’ll cover the basic usage in this article. Expect future articles that explore the finer details of the framework.

As a preview, below is the “SpectrumApp” demo in the samples directory featuring the Spectrum, Slider, Toggle & Button components.

SpectrumApp

– press play button ( if other tabs contain flash content the audio visualizer may not work, close them and refresh this page )

Source Code & Examples
First of all, take a look at the new repo up on Github located here: https://github.com/makemachine/makemachine.actionscript

The framework that we’ll be looking at is kept in the makemachine/display/ directory. The class InterfaceElement is the base class for all components. It defines some basic functionality that you probably won’t need to concern yourself with in order to use the components. Though, it is important to note that the InterfaceElement does implement a render cycle to prevent re-drawing elements more than needed.

Next, take a look in the /shapes/ directory. This code is used to draw basic shapes such as rectangles, polygons and circles. The shapes are used in virtually all of the user interface components. Shapes can have many of their graphical properties changed such as gradient fill colors, line style, bitmap fill & corner radius ( on rectangles ). Below is the shapes demo from the samples folder.

ShapesApp

You can see in the example above that there are all kinds of shapes, from circles to rounded rectangles and wedges. As it is for all InterfaceElement sub-classes the instantiation syntax is very simple. The following code will create and add a WedgeShape object to the supplied container. Note that the first parameter is the parent object and should be of type DisplayObjectContainer. If it is null, you will need to add the new object manually using addChild(), otherwise the object is added to the first constructor argument.

var wedge:WedgeShape = new WedgeShape( container );

You can also define the position of the newly created object by specifying an x and y coordinate in the constructor.

var wedge:WedgeShape = new WedgeShape( container, 100, 100 );

These shapes are used extensively in all of the user interface elements. There is another class that is also used extensively and this is the BitmapText class located in the /display/text/ directory. BitmapText wraps a standard Flash TextField and exposes an API for all of it’s properties. The difference is that the TextField is rendered as a Bitmap. Wrapping the class and rendering it as bitmap allows for better control of when the text is re-drawn. BitmapText can be generated using the Factory class, like so:

var text:BitmapText = Factory.singleLineField( container, xpos, ypos,
                                               "stylename", "initial text" );

Similarly a multi-line field can be create like so:

var text:BitmapText = Factory.singleLineField( container, xpos, ypos, width,
                                               "stylename", "initial text" );

Next, let’s take a look at a few examples using actual components.

HelloButtonApp

public function HelloButtonApp()
{
	var btn:Button = new Button( this, 10, 10 );
	btn.setText( "Hello Button" );
	btn.callback = helloButton;
	btn.data = "Generic data object attached to button";
}

protected function helloButton( btn:Button ):void
{
	trace( btn.data );
}

As you can see, this is not very complex. A Button object is created. The parent is defined with the first param and it will be positioned at ( 10, 10 ) according to the second and third params. The text is set using the setText() method. A callback is set to be triggered when the Button is pressed and some generic data is attached to the Button. You may have noticed that there is no call to addChild(). This is not necessary with you supply the parentContainer ( first ) parameter of any InterfaceElement sub-class. When this parameter is supplied the component is automatically added to the display list.

ButtonBarApp

public function ButtonBarApp()
{
	var btn:Button;
	var buttons:Vector.<Button> = new Vector.<Button>();
	bar = new ButtonBar( this, 10, 10 );
	var icons:Vector. = Vector.( [ Icons.SineWaveIcon,
						     Icons.TriangleWaveIcon,
   						     Icons.SquareWaveIcon,
						     Icons.SawtoothDownIcon,
						     Icons.SawtoothUpIcon,
						     Icons.BatmanIcon,
						     Icons.NoiseIcon ] );

	for( var i:int = 0; i < icons.length; i++ )
	{
		btn = new Button();
		btn.setIcon( new icons[i]() );
		buttons.push( btn );
	}

	bar.setContent( buttons );
        // -- buttons will auto size without setting an explicit size
	bar.setExplicitButtonSize( 30, 30 );
	bar.addEventListener( Event.SELECT, onButtonBarSelect );
}

protected function onButtonBarSelect( event:Event ):void
{
	trace( bar.selectedIndex );
}

This one requires a little more code. The ButtonBar component allows multiple buttons to be used as a selection box. Only one item in a button bar can be selected at a time. The generic Button class can have either a text or graphic label. Any graphic can be used with the setIcon() method, however, the above code is setting one of the built in icons as the label. The framework comes with a handful of built-in icons. I’ll be adding more icons as time goes by. There is an Adobe Illustrator file included in the repo that contains the actual art for these icons.

One of the key concepts in the framework is the use of Parameter objects. Parameters wrap numerical values, can be given names and assigned a unit of measure. Below is example of how the Parameter object is used:

HelloParametersApp

public function HelloParametersApp()
{
	var slider:Slider = new Slider( this, 10, 10 );

	// -- Notice how when the slider is adjusted the value turns from "Hz" to "kHz"
	var param1:Parameter = new Parameter( "Frequency", 200, 2000, 250 );
	param1.units = "Hz";
	param1.addEventListener( Event.CHANGE, onParamUpdate );
	slider.parameter = param1;
	slider.validate();

	var knob1:Knob = new Knob( this, slider.x, slider.y + slider.height + 5 );
	knob1.parameter = param1;
	knob1.validate();

	var knob2:Knob = new Knob( this, knob1.x + knob1.width + 1, knob1.y );
	var param2:Parameter = new Parameter( "Amplitude", 0.0, 1.0, .5 );
	knob2.parameter = param2;
	knob2.validate();
}

protected function onParamUpdate( event:Event ):void
{
	if( event.target is Parameter )
	{
		var param:Parameter = event.target as Parameter;
		trace( int( param.value ) );
	}
}

When a component such as a Knob or a Slider has it’s .parameter property set, it automatically takes on the properties of that Parameter. It’s label is changed to display the name of the Parameter and it’s value field displays a value between the min and max values of the Parameter. Text for values is trimmed to the nearest two or three decimal places and if the value exceeds 1000 it is truncated and has a lower case “k” added it it.

In the audio library I’m working on, Parameters are used throughout many of the processors and filters. Being able to pass around references to parameters is really nice in that you are not bound to static class variables. The concept is a little abstract but it really speeds up development and has many advantages. I’ll be writing more on the use of Parameters in future articles about sound programming.

No user interface framework is complete without some sort of auto-layout classes. There are two container classes, the XBox and the YBox. These containers display InterfaceElement objects in either horizontal or vertical layouts.

ContainersApp

public function ContainersApp()
{
	var ybox:YBox = new YBox( this, 5, 5 );

	// -- label size is based on constant values in the Factory class
	var label:Label = new Label( ybox );
	label.setText( "Controls" );

	// -- notice how the two knobs fit nicely below the label
	var xbox:XBox = new XBox( ybox, 0, 0 );

	var knob1:Knob = new Knob( xbox );
	var knob2:Knob = new Knob( xbox );

	var slider:Slider = new Slider( ybox );
}

The above code is pretty straightforward. It is using a YBox to stack all of the elements vertically and an XBox horizontally stack the two knobs.

Well, this article is getting lengthy, so it shall now end. I’m really looking forward to developing this further as I use it in more sound programming experiments. I’ll also be writing more posts soon about how to customize the components and other features as time permits.

Posted in Actionscript, Audio | 1 Comment

One Response to ActionScript 3.0 UI Elements

  1. Tim says:

    Nice!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>