Search
Close this search box.
Search
Close this search box.

How to draw an OS X gel button using ActionScript?

Share This Post

Recently, I completed a project in Flash that required all of the graphic elements to be rendered as vector shapes. I decided to use ActionScript only to draw every interface element. Why? Well, as our French friends say, “For the challenge!” There are practical reasons too involving control over look and feel of the page and other esoteric issues, but that is for another post.
What’s important is that I developed a burning desire to add Apple gel-type buttons to my interface (I like the way they look, so sue me), and I searched online for ways to create these buttons using vector tools. I found many links like this and this and this, but none addressed creating the buttons using the Flash Drawing API. So I studied each tutorial, absorbed the process, then automated it. And I will show you how I did it.

In order to fully understand how this works, you need to understand how the Drawing API works. Suffice it to say, there are 8 or so methods used in the Drawing API:
* lineStyle()
* moveTo()
* lineTo()
* curveTo()
* beginFill()
* beginGradientFill()
* endFill()
* clear()
To learn more about them, try this tutorial, or read the documentation included in Flash. I’m approaching this from a You-know-just-enough-to-be-dangerous perspective, which is how I approach cooking, parenting, and other heavy lifting exercises.
So to start out, I need to draw a circle at the right size with the right color. Since I started this out in AS2, I had to build my own circle drawing function, but fortunately found one in the Flash documentation that worked pretty well. I adjusted the arguments passed to it to include an object called details, so as to be able to draw a gradient or solid circle:

var DrawCircle = function(target_mc:MovieClip, radius:Number, details:Object):Void {
var x:Number = radius;
var y:Number = radius;
var fillType:String = details.fillType;
var fillColor:Number = details.fillColor;
var fillAlpha:Number = details.fillAlpha;
var matrix:Matrix = details.matrix;
var alphas:Array = details.alphas;
var ratios:Array = details.ratios;
var colors:Array = details.colors;
with (target_mc) {
if (fillType == "gradient") {
beginGradientFill("linear", colors, alphas, ratios, matrix);
}
else {
beginFill(fillColor, fillAlpha);
}
moveTo(x + radius, y);
curveTo(radius + x, Math.tan(Math.PI / 8) * radius + y, Math.sin(Math.PI / 4) * radius + x, Math.sin(Math.PI / 4) * radius + y);
curveTo(Math.tan(Math.PI / 8) * radius + x, radius + y, x, radius + y);
curveTo(-Math.tan(Math.PI / 8) * radius + x, radius+ y, -Math.sin(Math.PI / 4) * radius + x, Math.sin(Math.PI / 4) * radius + y);
curveTo(-radius + x, Math.tan(Math.PI / 8) * radius + y, -radius + x, y);
curveTo(-radius + x, -Math.tan(Math.PI / 8) * radius + y, -Math.sin(Math.PI / 4) * radius + x, -Math.sin(Math.PI / 4) * radius + y);
curveTo(-Math.tan(Math.PI / 8) * radius + x, -radius + y, x, -radius + y);
curveTo(Math.tan(Math.PI / 8) * radius + x, -radius + y, Math.sin(Math.PI / 4) * radius + x, -Math.sin(Math.PI / 4) * radius + y);
curveTo(radius + x, -Math.tan(Math.PI / 8) * radius + y, radius + x, y);
endFill();
}
}

You’ll notice that the arguments are the target movieclip(target_mc), the radius of the circle and the details of the circle, which include the line style, the fill type, color information, etc. So below this block, I put in the call to draw the circle:

// draw the color layer
var colorLayer = mcTarget.createEmptyMovieClip("colorLayer", mcTarget.getNextHighestDepth());
DrawCircle(colorLayer,7.5,{fillColor:bgColor, fillAlpha:100});

Next, I’ll want to add a layer that contains a gradient, right on top of the colored circle, but before I do that I have to add this line at the top of my script:

import flash.geom.*;

Now I can draw my gradient layer, without throwing an error:

// draw the gradient layer
var gradientLayer = mcTarget.createEmptyMovieClip("gradientLayer", mcTarget.getNextHighestDepth());
var gradientLayerMatrix:Matrix = new Matrix();
gradientLayerMatrix.createGradientBox(15, 15, Math.PI/2, 0, 0);
DrawCircle(gradientLayer,7.5,{fillType:"gradient", colors:[0x000000, 0xffffff], alphas:[75, 0], ratios:[0,230], matrix:gradientLayerMatrix});

Again I used the DrawCircle function to do the heavy lifting for me, after initially creating the gradient layer. I used the details object to pass specific information needed to draw the gradient (alphas, ratios, matrix and colors). Notice that the alphas go from 60 to 0, and the rotation set in the gradientLayerMatrix createGradientBox function is set to Math.PI/2. What this does, essentially, is to draw a gradient from top to bottom going from 60% black down to transparent, because everyone knows that PI/2 in radians is the same as 90°.
Lastly, I need to draw a highlight layer. This is the doo-hickey that adds the gleam of life to the button:

var shineLayer = mcTarget.createEmptyMovieClip("shineLayer", mcTarget.getNextHighestDepth());
var shineLayerMatrix:Matrix = new Matrix();
shineLayerMatrix.createGradientBox(15, 15, 90, 0, 0);
DrawCircle(shineLayer,7.5,{fillType:"gradient", colors:[0xffffff, 0xffffff], alphas:[100, 0], ratios:[0,0xff], matrix:shineLayerMatrix});

Notice that this layer will completely cover up the other two, and needs to be resized. It needs to be much smaller than the button, so I set the xscale and yscale to 25%. Then I center the gleam horizontally and adjust it’s position vertically:

shineLayer._xscale = 25;
shineLayer._yscale = 25;
shineLayer._alpha = 100;
shineLayer._x += (colorLayer._width - shineLayer._width)/2;
shineLayer._y +=2.6;

Run it in Flash, and frankly, it looks a little flat. That’s the nice thing about Apple’s gel buttons, they look rubbable. We need to add some dimensionality to our button, so we turn to the filters classes. Filters are a class of objects that can be applied to movieclips, textfields and other objects to create simple visual effects. To use the filters, we have to add the class to the script at the top like this:

import flash.filters.*

In order to add the filters at runtime, you have to add them to an array and replace the movieclip’s filters property with the array. You can’t simply just add a filter directly to the filters property. Why? Go ask Adobe. Hate the game, not the player.
So, we want a drop shadow surrounding the whole thing, and let’s add a bevel to give it a little oomph. And since the drop shadow is directional, we’ll use the glow:

// add filters
var myButtonBevelFilter:BevelFilter = new BevelFilter(1);
myButtonBevelFilter.blurX = myButtonBevelFilter.blurY = 0;
var myButtonGlowFilter:GlowFilter = new GlowFilter(0x000000, 100,5,5,1.25,3,true,false);

Apply it to the bottommost layer, the color layer:

var colorLayerFilters:Array = new Array(myButtonBevelFilter, myButtonGlowFilter);
colorLayer.filters = colorLayerFilters;

And we’re done. Wrap this up in a function (replacing the this keyword with a target_mc argument, and the color layer color value with another argument), create an empty movieclip to draw the button in, then draw and reposition, and we get this:

import flash.geom.*;
import flash.filters.*

var DrawCircle = function(target_mc:MovieClip, radius:Number, details:Object):Void {
var x:Number = radius;
var y:Number = radius;
var fillType:String = details.fillType;
var fillColor:Number = details.fillColor;
var fillAlpha:Number = details.fillAlpha;
var matrix:Matrix = details.matrix;
var alphas:Array = details.alphas;
var ratios:Array = details.ratios;
var colors:Array = details.colors;

with (target_mc) {
if (fillType == "gradient") {
beginGradientFill("linear", colors, alphas, ratios, matrix);
}
else {
beginFill(fillColor, fillAlpha);
}
moveTo(x + radius, y);
curveTo(radius + x, Math.tan(Math.PI / 8) * radius + y, Math.sin(Math.PI / 4) * radius + x, Math.sin(Math.PI / 4) * radius + y);
curveTo(Math.tan(Math.PI / 8) * radius + x, radius + y, x, radius + y);
curveTo(-Math.tan(Math.PI / 8) * radius + x, radius+ y, -Math.sin(Math.PI / 4) * radius + x, Math.sin(Math.PI / 4) * radius + y);
curveTo(-radius + x, Math.tan(Math.PI / 8) * radius + y, -radius + x, y);
curveTo(-radius + x, -Math.tan(Math.PI / 8) * radius + y, -Math.sin(Math.PI / 4) * radius + x, -Math.sin(Math.PI / 4) * radius + y);
curveTo(-Math.tan(Math.PI / 8) * radius + x, -radius + y, x, -radius + y);
curveTo(Math.tan(Math.PI / 8) * radius + x, -radius + y, Math.sin(Math.PI / 4) * radius + x, -Math.sin(Math.PI / 4) * radius + y);
curveTo(radius + x, -Math.tan(Math.PI / 8) * radius + y, radius + x, y);
endFill();
}
}

var drawButton = function(target_mc:MovieClip, bgColor:Number) {
// draw the color layer
var colorLayer = target_mc.createEmptyMovieClip("colorLayer", target_mc.getNextHighestDepth());
DrawCircle(colorLayer,7.5,{fillColor:bgColor, fillAlpha:100});

var myButtonBevelFilter:BevelFilter = new BevelFilter(1);
myButtonBevelFilter.blurX = myButtonBevelFilter.blurY = 0;
var myButtonGlowFilter:GlowFilter = new GlowFilter(0x000000, 100,5,5,1.25,3,true,false);

var colorLayerFilters:Array = new Array(myButtonBevelFilter, myButtonGlowFilter);
colorLayer.filters = colorLayerFilters;

// draw the gradient layer
var gradientLayer = target_mc.createEmptyMovieClip("gradientLayer", target_mc.getNextHighestDepth());
var gradientLayerMatrix:Matrix = new Matrix();
gradientLayerMatrix.createGradientBox(15, 15, 90, 0, 0);
DrawCircle(gradientLayer,7.5,{fillType:"gradient", colors:[0x000000, 0xffffff], alphas:[60, 0], ratios:[0,126], matrix:gradientLayerMatrix});

var shineLayer = target_mc.createEmptyMovieClip("shineLayer", target_mc.getNextHighestDepth());
var shineLayerMatrix:Matrix = new Matrix();
shineLayerMatrix.createGradientBox(15, 15, 90, 0, 0);
DrawCircle(shineLayer,7.5,{fillType:"gradient", colors:[0xffffff, 0xffffff], alphas:[100, 0], ratios:[0,0xff], matrix:shineLayerMatrix});
shineLayer._xscale = 25;
shineLayer._yscale = 25;
shineLayer._alpha = 100;
shineLayer._x += (colorLayer._width - shineLayer._width)/2;
shineLayer._y +=2.6;
}

var gelButton = this.createEmptyMovieClip("gelButton", this.getNextHighestDepth());
this.drawButton(gelButton, 0x82FE81);
gelButton._x = gelButton._y = 25;

[kml_flashembed movie=”https://currentmarketing.com/insidecm/wp-content/uploads/2008/02/gel-button.swf” height=”65″ width=”65″ /]
It doesn’t look half bad. Adjusting the gradient and gleam values will undoubtedly yield a better looking button, but I’ll leave that to someone with a little more design acumen.

More To Explore

Current360 2024 Predictions crystal ball

2024 Predictions

Thanks to everyone who responded to our 2024 Predictions survey last month. While the sample size wasn’t quite the size of a Pew or Nielsen,

Tradition and digital media crown become digital

The King is dead.
Long Live the King.

No we’re not talking about Charles VII or his father Charles VI. Instead, we’re recognizing the passing of the baton after years of shifts from

Contact Us

"*" indicates required fields

I am not a robot
Ed Sharp Current360 headshot

Ed Sharp

Ed brings 15 years of traditional and digital media sales experience to the agency, giving us a perspective most agencies don’t have. When he’s not working or seeking new knowledge, Ed hangs out with his wife, two kids, two dogs, one cat, and a hamster. And yes, the cat and hamster are best friends.

Chaney Given

Chaney is a talented and accomplished designer and illustrator, who has expanded his skill set to include motion graphics and video editing. With nearly a decade of experience, his client work includes Waterstep, Baptist Health, the Archdiocese of Louisville Catholic Schools, First Harrison Bank, and many more