The most complex piece of visual design in my project is the custom
buttons. I'm attaching the code for them so you can see how I
approached it.
Basically, each button is a partly transparent (50%) horizontal bar with
lettering in the center and a border around it. The right side of the
bar extends some distance, gradually fading to 0% via a LinearGradient.
The button changes color when hovered over.
The ButtonStyles object is just a wrapper around an array of parameters,
that specify the dimensions, color, margin, etc. of the buttons, so that
I can have several different kinds of buttons, all drawn by the same
base class.
But I've been reviewing the book I'm learning from, and it talks about
custom and skinnable components. And I'm wondering if I really need all
this code. Maybe I can do all this with a skinnable custom component
instead? Then I can statically "draw" the main menu and list of options
in MXML. That's one of my goals: to make as much of the layout/visual
component of the project static as possible.
What do people think? Is this doable? Is it any simpler than the class
I wrote? Or does it make things more complex instead of simpler?
package Displayable
{
import flash.events.MouseEvent;
import flash.display.*;
import mx.graphics.GradientEntry;
import mx.graphics.LinearGradient;
import spark.components.*;
import support.*;
import flash.text.TextFormat;
import flash.text.TextFieldAutoSize;
import flash.geom.*;
/**
* My "button" consisting of a horizontal bar with a border around it and
* some text over it.
*/
public class HBarButton extends Sprite
{
public var text; // The text of the button
private var clickFN:Function; // The callback object
private var hgroup:HGroup; // group containing the bar (and fadeout)
private var dispGroup:Group; // group that everything is drawn in
private var styles:ButtonStyles;
private var fadeoutWidth:int;
/**
* Constructor.
* @param btext The button's label
* @param clickFN Called when the button is clicked
*/
public function HBarButton(msgtext: String, click: Function)
{
testAbstract();
text = msgtext;
clickFN = click;
// inherited properties
buttonMode = true;
styles = new ButtonStyles();
fadeoutWidth = 0;
}
public function setStyles(newStyles:ButtonStyles): void
{
styles = newStyles;
}
public function getStyle(which: int):Object
{
return styles.getValue(which);
}
public function getIntStyle(which: int):int
{
return styles.getIntValue(which);
}
public function getStringStyle(which: int):String
{
return styles.getStringValue(which);
}
public function setStyle(which: int, val: int): void
{
styles.setValue(which, val);
}
/**
* Make this class abstract by preventing it from being instantiated.
* Must be overridden in child (non-abstract) classes.
*/
protected function testAbstract(): void
{
throw new NotImplementedError(
"Can't instantiate abstract class: HBarButton" );
}
public function draw(): void
{
// Create the bar
drawBar();
if (styles.getIntValue(ButtonStyles.BORDER_HEIGHT) > 0) {
drawBorders();
}
// Add the lettering
drawText();
// Add the fadeout, if there is one.
if (fadeoutWidth > 0) {
drawFadeout();
}
setupEvents();
}
protected function drawBar(color = null)
{
// get dimensions
var barx = 0;
var bary = styles.getIntValue(ButtonStyles.BORDER_HEIGHT);
var barht = styles.getIntValue(ButtonStyles.BAR_HEIGHT);
var barwd = styles.getIntValue(ButtonStyles.BAR_WIDTH);
if (color == null) {
color = styles.getIntValue(ButtonStyles.BAR_COLOR);
}
// Set up the fill and draw the rectangle
graphics.beginFill(color);
graphics.drawRect(barx, bary, barwd, barht);
graphics.endFill();
}
protected function drawBorders()
{
// get dimensions
// Borders start at x=0; top border starts at y=0, bottom
// border at y = border height + bar height.
var x = 0;
var topy = 0;
var bdwd = styles.getIntValue(ButtonStyles.BAR_WIDTH);
var bdht = styles.getIntValue(ButtonStyles.BORDER_HEIGHT);
var barht = styles.getIntValue(ButtonStyles.BAR_HEIGHT);
var bdcolor = styles.getIntValue(ButtonStyles.BORDER_COLOR);
var boty = bdht + barht;
// Draw the top and bottom border lines
graphics.lineStyle(bdht, bdcolor);
graphics.moveTo(x, topy);
graphics.lineTo(bdwd, topy);
graphics.moveTo(x, boty);
graphics.lineTo(bdwd, boty);
}
protected function drawText()
{
// "Style" the text the way I want it to look
var myFormat:TextFormat = new TextFormat();
myFormat.align = styles.getStringValue(ButtonStyles.TEXT_ALIGN);
myFormat.color = styles.getStringValue(ButtonStyles.FONT_COLOR);
myFormat.font = styles.getStringValue(ButtonStyles.FONT_NAME);
myFormat.size = styles.getIntValue(ButtonStyles.FONT_SIZE);
myFormat.leftMargin = styles.getIntValue(ButtonStyles.MARGIN_RIGHT);
myFormat.rightMargin = styles.getIntValue(ButtonStyles.MARGIN_RIGHT);
// Create a label and put the text into it.
var label = new Label();
label.setStyle("textFormat", myFormat);
label.text = text;
label.autoSize = TextFieldAutoSize.CENTER;
// Add the label to this sprite
addChild(label);
}
protected function drawFadeout()
{
// Dimensions of the Fading rectangle
var fadex = styles.getIntValue(ButtonStyles.BAR_WIDTH);
var fadey = styles.getIntValue(ButtonStyles.BORDER_HEIGHT);
var fadewd = styles.getIntValue(ButtonStyles.FADEOUT_WIDTH);
var fadeht = styles.getIntValue(ButtonStyles.BAR_HEIGHT);
// Gradient fill, from normal to zero alpha
var fill:LinearGradient = new LinearGradient();
var barColor:int = int(styles.getIntValue(ButtonStyles.BAR_COLOR));
var g1: GradientEntry = new GradientEntry(barColor, barColor, 1.0);
var g2: GradientEntry = new GradientEntry(barColor, barColor, 0.0);
fill.entries = [g1, g2];
// Draw the rectangle
var fadeRectangle = new Rectangle(fadex, fadey, fadewd, fadeht);
var fadeOrigin = new Point(0, 0);
fill.begin(graphics, fadeRectangle, fadeOrigin);
graphics.lineStyle(NaN, 0, 0);
graphics.drawRect(fadex, fadey, fadewd, fadeht);
fill.end(graphics);
}
public function addObject(obj:DisplayObject): void
{
throw new Error("addObject not implemented");
}
protected function setupEvents(): void
{
addEventListener(MouseEvent.CLICK, clickFN);
addEventListener(MouseEvent.MOUSE_OVER, MouseOverHandler);
addEventListener(MouseEvent.MOUSE_OUT, MouseOutHandler);
addEventListener(MouseEvent.MOUSE_DOWN, MouseDownHandler);
addEventListener(MouseEvent.MOUSE_UP, MouseUpHandler);
}
private function MouseOverHandler(): void
{
drawBar(styles.getIntValue(ButtonStyles.HOVER_COLOR));
}
private function MouseOutHandler(): void
{
drawBar(styles.getIntValue(ButtonStyles.BAR_COLOR));
}
private function MouseDownHandler(): void
{
}
private function MouseUpHandler(): void
{
}
}
} // end package
package Displayable
{
import Displayable.Appearance;
public class ButtonStyles
{
public static const BAR_COLOR:int = 0; // The color of the bar
public static const HOVER_COLOR:int = 1; // Color when hovered over
public static const PUSH_COLOR:int = 2; // Color when button
pressed
public static const BORDER_HEIGHT:int = 3;
public static const BORDER_COLOR:int = 4; // The color of the borders
public static const FONT_COLOR:int = 5;
public static const FONT_SIZE:int = 6;
public static const FONT_NAME:int = 7;
public static const BAR_HEIGHT:int = 8;
public static const BAR_WIDTH:int = 9;
public static const TOTAL_HEIGHT:int = 10;
public static const MARGIN_LEFT:int = 11;
public static const MARGIN_RIGHT:int = 12;
// Fadeout width:if non-zero, the bar is extended with a gradient
// that reduces the alpha to zero so that it fades into the
// background.
public static const FADEOUT_WIDTH:int = 13;
// Inherited properties that I use
public static const ALPHA:int = 14;
public static const BUTTONMODE:int = 15;
public static const TEXT_ALIGN:int = 16;
private static const ARRAY_LENGTH:int = 17;
private var theValues:Array;
public function ButtonStyles()
{
theValues = new Array(ARRAY_LENGTH);
theValues[BAR_COLOR] = 0x000000; // default = black
theValues[HOVER_COLOR] = Appearance.BUTTON_DEFAULT_HOVER_COLOR;
theValues[PUSH_COLOR] = Appearance.BUTTON_DEFAULT_PUSH_COLOR;
theValues[BORDER_COLOR] = Appearance.BUTTON_DEFAULT_BORDER_COLOR;
theValues[FONT_COLOR] = Appearance.BUTTON_DEFAULT_FONT_COLOR;
theValues[FONT_SIZE] = Appearance.BUTTON_DEFAULT_FONT_SIZE;
theValues[FONT_NAME] = Appearance.BUTTON_DEFAULT_FONT_NAME;
theValues[BAR_HEIGHT] = -1;
theValues[BAR_WIDTH] = -1;
theValues[TOTAL_HEIGHT] = -1;
theValues[MARGIN_LEFT] = 0;
theValues[MARGIN_RIGHT] = 0;
theValues[FADEOUT_WIDTH] = 0;
theValues[BORDER_HEIGHT] = 0;
theValues[ALPHA] = 50; // 50%
theValues[TEXT_ALIGN] = "left";
}
public function getValue(which:int):Object
{
var val:int = theValues[which];
if (val < 0) {
throw new RangeError("unset ButtonStyle: " + which);
}
return val;
}
public function getStringValue(which:int):String
{
return String(getValue(which));
}
public function getIntValue(which:int):int
{
return int(getValue(which));
}
public function setValue(which:int, val:Object):void
{
theValues[which] = val;
}
}
}