Tailor Your Flash Workspace by Creating Custom Panels

Tailor Your Flash Workspace by Creating Custom Panels

Tutorial Details
  • Program: Flash CS3 +
  • Difficulty: Intermediate
  • Estimated Completion Time: 45 mins

In my last tut: Create New Features for Flash with JSFL we created new commands for Flash. Now we’re going to take things further by creating entirely new panels within the Flash authoring environment.


Final Result Preview

To try the Buttonizer panel, download the Source zip file and extract the Buttonizer.swf file to the folder listed in Step 3. Restart Flash and you’ll find it in Window > Other Panels.


Step 1: Create Panel SWF

A Flash panel is just a regular SWF that you tell Flash to run in a panel rather than a separate window. All you need to do is create a new FLA. The name of the FLA will be displayed as the title of the panel; I’m calling mine Buttonizer.fla. We’ll use AS3 for this tutorial.


Step 2: Populate Your Panel

A plain white panel’s pretty useless, of course, so let’s fill it out. Resize the stage to 250px wide by 170px high (not that it matters – I’ve picked these numbers because I know they’re big enough for what I have planned later) and change the background color to #EDEDED (this matches the backgrounds of the panels on my system, anyway).

Open the Components panel (Window > Components) and drag a Button from the User Interface folder onto the stage.


Step 3: Turn the SWF into a Panel

Compile a SWF from this FLA. To get Flash to use this as a panel, all you have to do is drag the SWF into the correct folder, then restart Flash.

The folder is called WindowSWF and its location varies depending on your operating system:

  • Mac OS X: [hard drive]/Users/userName/Library/Application Support/Adobe/Flash CS3/language/Configuration/WindowSWF
  • Windows XP: [boot drive]\Documents and Settings\username\Local Settings\Application Data\Adobe\Flash CS3\language\Configuration\WindowSWF
  • Windows Vista: [boot drive]\Users\username\Local Settings\Application Data\Adobe\Flash CS3\language\Configuration\WindowSWF
  • Windows Vista (alt): [boot drive]\Users\username\AppData\Local\Adobe\Flash CS3\language\Configuration\WindowSWF
  • Windows 7: [boot drive]\Users\username\Local Settings\Application Data\Adobe\Flash CS3\language\Configuration\WindowSWF
  • Windows 7 (alt): [boot drive]\Users\username\AppData\Local\Adobe\Flash CS3\language\Configuration\WindowSWF

The username folder will match the name you use to log in with, language will change depending on what you picked when you installed Flash (for English speakers it’ll probably be en-us or just en), and if you’re using a newer version of Flash than CS3, that folder will change too.

(Frankly, it’s probably easiest to search your computer to find all folders named WindowSWF. One will be correct.)

Drag your SWF over to the WindowSWF folder then restart Flash. Don’t worry, you won’t have to restart Flash every time you make a change. Once you’ve opened it again, check out the Window > Other Panels sub-menu and you should see Buttonizer as an option. Click it:

Awesome.


Step 4: Change the Panel

With the panel open, move the button in the FLA, then re-compile the SWF. The panel doesn’t change.

Move the SWF to the WindowSWF folder. The panel still doesn’t change.

Close the panel and re-open it from the menu. It changes.

We can speed this up by having the panel’s SWF publish directly to the WindowSWF folder. Click File > Publish Settings, then click the folder icon next to the box that says Buttonizer.swf, browse to your WindowSWF folder and hit Save.

Uncheck the HTML checkbox, too; you don’t need it.

Now move the button again, recompile the SWF (you can hit Shift-F12 to do this without making Flash Player appear), close the panel, re-open it, and it will have updated.


Step 5: Run Some Code in the Panel

As the panel is a functioning SWF, we’re not restricted to changing how it looks; we can add code, too.

Create a new AS file called Buttonizer.as, and set it up as the document class of your FLA. Here’s the basic code:

package
{
	import flash.display.MovieClip;
	public class Buttonizer extends MovieClip
	{
		public function Buttonizer()
		{
			
		}
	}
}

(If you’re not familiar with using a document class, check out this quick introduction.)

To prove code can work, we’ll change the text on the button. In the FLA, give the button an instance name of theButton (imaginative), then in your constructor function, add this line of code:

theButton.label = "Buttonize";

Hit Shift-F12, close and re-open the panel, and you’ll see the text has changed.

If not, then check you’ve linked the FLA to the document class and named the button.


Step 6: Make the Button Do Something

Let’s get a function in there to handle the button being pushed:

package
{
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	
	public class Buttonizer extends MovieClip
	{
		public function Buttonizer()
		{
			theButton.label = "Buttonize";
			theButton.addEventListener( MouseEvent.CLICK, onClickTheButton );
		}
		
		private function onClickTheButton( a_event:MouseEvent ):void
		{
			trace( "Button clicked" );
		}
	}
}

Nothing complicated there. I’ve used a trace() to make sure it’s all hooked up correctly. Reload the panel and make sure it works.

Oh… it doesn’t?

That’s right; the AS3 trace() function doesn’t trace results to the Output panel when run from within another panel. I hate working without trace(), so we’ll have to get around this somehow.


Step 7: Debugging without Trace()

In my JSFL tutorial, I showed you the JavaScript fl.trace() function. It traces text to the Output panel, but is run within the Flash authoring environment itself, rather than from a Flash Player window. That’s great – it means we can run it from within our panel!

But we can’t just type fl.trace( “Button clicked” ); within our AS3 code, because it’s not an AS3 function. We have to tell Flash to run this as JSFL, and to do that we use the adobe.utils.MMExecute() function, which is AS3:

package
{
	import adobe.utils.MMExecute;		//don't forget this!
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	
	public class Buttonizer extends MovieClip
	{
		public function Buttonizer()
		{
			theButton.label = "Buttonize";
			theButton.addEventListener( MouseEvent.CLICK, onClickTheButton );
		}
		
		private function onClickTheButton( a_event:MouseEvent ):void
		{
			MMExecute( "fl.trace( 'Button clicked' );" );	//quotes in quotes get confusing
		}
	}
}

MMExecute() takes a string and runs it as a JSFL call. It’s completely ignored by the Flash Player window.

If you test this out now, clicking the button will trace to the Output panel. Excellent.


Step 8: Run the Buttonize JSFL Script

It would be inconvenient to take a longer script and push it through an MMExecute() call. Instead, we can save the JSFL to a script file and tell Flash to run that.

If you followed my JSFL tutorial, you’ll have a Buttonize.jsfl file already; if not, copy the following code to a new JSFL file:

if ( fl.getDocumentDOM().selection.length == 1 )
{
	if ( fl.getDocumentDOM().selection[0].elementType == "text" )
	{
		var textLeft = fl.getDocumentDOM().selection[0].x;
		var textTop = fl.getDocumentDOM().selection[0].y;
		var textRight = fl.getDocumentDOM().selection[0].x + fl.getDocumentDOM().selection[0].width;
		var textBottom = fl.getDocumentDOM().selection[0].y + fl.getDocumentDOM().selection[0].height;
		var textText = fl.getDocumentDOM().selection[0].getTextString();
		
		fl.getDocumentDOM().convertToSymbol('button', textText, 'top left');
		var lib = fl.getDocumentDOM().library;
		if (lib.getItemProperty('linkageImportForRS') == true) {
		lib.setItemProperty('linkageImportForRS', false);
		}
		else {
		lib.setItemProperty('linkageExportForAS', false);
		lib.setItemProperty('linkageExportForRS', false);
		}
		lib.setItemProperty('scalingGrid',  false);
		
		fl.getDocumentDOM().enterEditMode('inPlace');
		fl.getDocumentDOM().getTimeline().convertToKeyframes();
		fl.getDocumentDOM().getTimeline().convertToKeyframes();
		fl.getDocumentDOM().getTimeline().convertToKeyframes();
		fl.getDocumentDOM().addNewRectangle({left:textLeft, top:textTop, right:textRight, bottom:textBottom}, 0);
	}
}

Save it as Buttonize.jsfl anywhere on your hard drive. Now you can run that script by calling (in AS3):

MMExecute( "fl.runScript( '(path-to-your-script)' + '/Buttonize.jsfl' )" );

Step 9: Move the Script

To keep things simple, move your JSFL file into your WindowSWF directory. You can now replace ‘(path-to-your-script)’ with fl.configURI + ‘WindowSWF/’. Let’s try it out:

package
{
	import adobe.utils.MMExecute;
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	
	public class Buttonizer extends MovieClip
	{
		public function Buttonizer()
		{
			theButton.label = "Buttonize";
			theButton.addEventListener( MouseEvent.CLICK, onClickTheButton );
		}
		
		private function onClickTheButton( a_event:MouseEvent ):void
		{
			MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl' )" );
		}
	}
}

Start a new FLA, create some text, select it, and hit the Buttonize button. It should turn into a button, just like the script does normally.


Step 10: Add a “Down” State ColorPicker

Back in the Buttonizer FLA, drag a ColorPicker and a Label from the User Interface components to the stage. We’ll use the color picker to change the color of the text in the button’s Down state. Arrange the components appropriately:

Give the color picker an instance name of downColorPicker.


Step 11: Encase the Script in a Function

We’ll need to pass the color from the ColorPicker to the Buttonize script, but first, we’ll turn the script into a function so that it can accept arguments.

Modify it like so:

function makeButtonFromText( downColor )
{
	if ( fl.getDocumentDOM().selection.length == 1 )
	{
		if ( fl.getDocumentDOM().selection[0].elementType == "text" )
		{
			var textLeft = fl.getDocumentDOM().selection[0].x;
			var textTop = fl.getDocumentDOM().selection[0].y;
			var textRight = fl.getDocumentDOM().selection[0].x + fl.getDocumentDOM().selection[0].width;
			var textBottom = fl.getDocumentDOM().selection[0].y + fl.getDocumentDOM().selection[0].height;
			var textText = fl.getDocumentDOM().selection[0].getTextString();
			
			fl.getDocumentDOM().convertToSymbol('button', textText, 'top left');
			var lib = fl.getDocumentDOM().library;
			if (lib.getItemProperty('linkageImportForRS') == true) {
			lib.setItemProperty('linkageImportForRS', false);
			}
			else {
			lib.setItemProperty('linkageExportForAS', false);
			lib.setItemProperty('linkageExportForRS', false);
			}
			lib.setItemProperty('scalingGrid',  false);
			
			fl.getDocumentDOM().enterEditMode('inPlace');
			fl.getDocumentDOM().getTimeline().convertToKeyframes();
			fl.getDocumentDOM().getTimeline().convertToKeyframes();
			fl.getDocumentDOM().getTimeline().convertToKeyframes();
			fl.getDocumentDOM().addNewRectangle({left:textLeft, top:textTop, right:textRight, bottom:textBottom}, 0);
		}
	}
}

Step 12: Pass the Color to the Function

Now we should alter the MMExecute() call, to refer to that specific function in the script. All that’s required is to pass the function name as a second parameter:

MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl', 'makeButtonFromText' )" );

For every argument we want to pass to the JSFL function, we just add another parameter to the MMExecute() call. So, to pass the selected color:

MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl', 'makeButtonFromText', " + downColorPicker.selectedColor.toString() + " )" );

We have to break out of the double quotes to include that argument, since we obtain it via AS3, not via JSFL. It’s messy, and more than a little confusing, I know.

Let’s add a simple trace to the JSFL function to make sure this is working:

function makeButtonFromText( downColor )
{
	fl.trace( "color: " + downColor );
	if ( fl.getDocumentDOM().selection.length == 1 )
	{

(Since it’s above the selection condition, you can see the trace without having to actually buttonize some text.)

I tried it with black and then white, and here’s what appeared in the Output panel:


color: 0
color: 16777215

Looks like it’s working to me.


Step 13: Color of the “Down” Text

To find out what JSFL to use to change the text color, use the trick from the JSFL tut; hit “Select All”, then change the fill color and take a look at the code in the History panel:

fl.getDocumentDOM().selectAll();
fl.getDocumentDOM().setFillColor('#009999');

Uh-oh. Are we going to have to convert the uint color we got from the ColorPicker into an HTML string? Fortunately, no; the document.setFillColor() help page tells us that we can use either format.

So, all we need to do is insert that new script in the correct place. Since the “Down” frame is the third one, we should insert it after the second convertToKeyframes() call:

fl.getDocumentDOM().enterEditMode('inPlace');
fl.getDocumentDOM().getTimeline().convertToKeyframes();

fl.getDocumentDOM().getTimeline().convertToKeyframes();
fl.getDocumentDOM().selectAll();				//new line
fl.getDocumentDOM().setFillColor( downColor );	//new line

fl.getDocumentDOM().getTimeline().convertToKeyframes();
fl.getDocumentDOM().addNewRectangle({left:textLeft, top:textTop, right:textRight, bottom:textBottom}, 0);

This works:


Step 14: Do the Same for the “Over” State

Add a new ColorPicker (overColorPicker) and Label to let the user change the color of the “Over” text:

Change the signature of the JSFL function to accept this other color:

function makeButtonFromText( overColor, downColor )

While we’re editing the JSFL function, we might as well add the script to change the color of the “Over” state:

fl.getDocumentDOM().enterEditMode('inPlace');
fl.getDocumentDOM().getTimeline().convertToKeyframes();
fl.getDocumentDOM().selectAll();
fl.getDocumentDOM().setFillColor( overColor );

Now let’s change the call to MMExecute() in the AS3 so that it passes along the “Over” color:

MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl', 'makeButtonFromText', " + overColorPicker.selectedColor.toString() + ", " + downColorPicker.selectedColor.toString() + " )" );

…Ugh. That’s too messy, and it’s going to get worse. How can we fix this?


Step 15: Thank the Stevens

Steven Sacks and Steven Hargrove came up with a neat little function to make this call easier. I’ve adapted it here:

private function runButtonizeScript( ... args ):String
{
	if ( args.length > 0 )
	{
		return MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl', 'makeButtonFromText', " + args.join(", ") + " );" );
	}
	else
	{
		return MMExecute( "fl.runScript( fl.configURI + 'WindowSWF/' + '/Buttonize.jsfl', 'makeButtonFromText' )" );
	}
}

So now we can call:

private function onClickTheButton( a_event:MouseEvent ):void
{
	runButtonizeScript( overColorPicker.selectedColor.toString(), downColorPicker.selectedColor.toString() );
}

Much neater!


Step 16: Do the Same with the “Up” State

Add new components:

Change signature of JSFL function:

function makeButtonFromText( upColor, overColor, downColor )

Make JSFL change color of text:

fl.getDocumentDOM().enterEditMode('inPlace');
fl.getDocumentDOM().selectAll();
fl.getDocumentDOM().setFillColor( upColor );

Pass “Up” color to JSFL from AS3:

private function onClickTheButton( a_event:MouseEvent ):void
{
	runButtonizeScript( upColorPicker.selectedColor.toString(), overColorPicker.selectedColor.toString(), downColorPicker.selectedColor.toString() );
}

Test it:

Brilliant. Just one thing left.


Step 17: Get Existing Color from JSFL

Chances are, the user is going to want to stick with the color they picked for the text for at least one of the three states. But we start all three ColorPickers with black as the selected color.

So, now we need to get the existing fill color from the text object, and use JSFL to pass it through to the panel, then use AS3 to change the color of the ColorPickers. It’s the opposite of what we’ve been doing, really.

Where in our AS3 code should we set the color of our pickers, though? I see three choices:

  • In an ENTER_FRAME handler: not a bad idea, but can cause the whole Flash app to lag when done in a panel.
  • In a MOUSE_CLICK handler for the entire panel: here we’d keep track of whether the user had changed any of the color pickers since selecting the text, and if not, would reset all three to match the text. A good method, but a bit beyond the scope of this tutorial.
  • In a MOUSE_CLICK handler for another button: simple, avoids accidents, easy for the user to understand what’s going on… we have a winner.

So, add a new Button to the Buttonizer, labeled “Set Defaults”. Give it an instance name of setDefaults.


Step 18: Add MOUSE_CLICK Handler

Back in Buttonizer.as:

public function Buttonizer()
{
	theButton.label = "Buttonize";
	theButton.addEventListener( MouseEvent.CLICK, onClickTheButton );
	setDefaults.addEventListener( MouseEvent.CLICK, onClickSetDefaults );
}

private function onClickSetDefaults( a_event:MouseEvent ):void
{
	
}

Step 19: Put Test Code in Handler

Just to make sure this will work when we have the correct data:

private function onClickSetDefaults( a_event:MouseEvent ):void
{
	var defaultColor:int = 0x000000;	//black
	upColorPicker.selectedColor = defaultColor;
	overColorPicker.selectedColor = defaultColor;
	downColorPicker.selectedColor = defaultColor;
}

Make sure that it works by setting some random colors, then clicking the Set Defaults button. It should reset them all to black.


Step 20: Get Color From JSFL

The MMExecute() function returns the return value of any function run through it. (If several functions are run, it returns the return value of the last one.)

To get the color of the selected text, we can use the JSFL document.getCustomFill( ‘selection’ ) function. This returns a Fill object, whose color property is what we need. So, we can get the color like so:

MMExecute( "document.getCustomFill( 'selection' ).color" );

This is actually not quite what we want, as it returns the color in CSS String format: “#AA43E2″, for example. I’ve written a little extra code to convert this to the uint format that we need:

private function onClickSetDefaults( a_event:MouseEvent ):void
{
	var cssFormatDefaultColor:String = MMExecute( "document.getCustomFill( 'selection' ).color" );
	cssFormatDefaultColor = cssFormatDefaultColor.replace( "#", "0x" );
	var defaultColor = uint( cssFormatDefaultColor );
	upColorPicker.selectedColor = defaultColor;
	overColorPicker.selectedColor = defaultColor;
	downColorPicker.selectedColor = defaultColor;
}

Try this out:

Awesome :)


Wrap Up

Take a look back at what you’ve learned. You can now create your own panels inside Flash, run JSFL commands and scripts, pass data from a panel to the stage, and even get data from the stage to use in the panel. Congrats!

  • André

    Hahahaha, now i understand why you said my comment was funny about the windowSWF and custom panels in comments of your other tutorial…

    Great tutorial Michael!!

  • http://psdho.me PSDhome

    Very nice tutorial. Thank you so much.

  • camilloesc

    wow wow wow!!!! but a lot of programation!!

  • http://www.e11world.com e11world

    Extremely useful tutorial. Thank you soo much for this!

  • Jack

    cool stuff man.

  • http://michaeljameswilliams.com/ Michael Williams
    Author

    Thanks all!

  • solid

    this is an amazing tutorial.
    eyeopening to what flash is capable of.
    thanks alot for sharing.

  • http://flanture.blogspot.com flanture

    This can be considered part II of your previous Tutorial on creating new features for Flash with JSFL, because without it, it’s little confusing. Anyway, great stuff, I would like to read more in same direction. Thanks.

    • http://michaeljameswilliams.com/ Michael Williams
      Author

      Thanks, Flanture! We’ve got more on JSFL lined up :)

  • Pingback: Do you wear a suit to the tailor or take it and change there? | Custom made suit

  • Pingback: Tailor-made exrecises and custom exercises mean the same?? Thx!!? | Custom made suit

  • http://gamedev.rasmuswriedtlarsen.com Rasmus Wriedt Larsen

    Awesome! This really expands the capabilities of using flash! simply just cool!

  • http://www.clippingimages.com/ Clippingimages

    Thanks man..this is a huge tutorial…

  • Mambo4

    I am curious as to why you need to pass “selectedColor.toString() ” instead of just “selectedColor”
    could you explain the reason?

    • http://michaeljameswilliams.com/ Michael Williams
      Author

      Hey Mambo4,

      The reason is, runButtonizeScript() builds a string from the arguments passed to it (this string is a function call which is then passed to MMExecute()). selectedColor is a uint, so it needs to be converted to a string.

      In retrospect, it would have been smarter to do the conversion within runButtonizeScript(), rather than before passing the argument to it :)

  • Tom

    Hey Micheal, great tutorial, however, when I try to run the MMExecute function, it gives me
    The following JavaScript error(s) occurred:

    TypeError: + is not a function

    that’s happening here right?

    MMExecute( “fl.runScript( fl.configURI + ‘WindowSWF/’ + ‘/Buttonize.jsfl’, ‘makeButtonFromText’, ” + downColorPicker.selectedColor.toString() + ” )” );

    Am I missing something? I mean I just copied this straight over from your tutorial after my third retype with the same error,and it gives me the same thing.

  • Tom

    Michael,

    Nevermind on my question, I figured it out, the actual problem was that I had return types on my functions in the JSFL file.

    Tom

    • http://michaeljameswilliams.com/ Michael Williams
      Author

      Ahh, gotcha. Glad you figured it out :) Thanks for posting the solution!

  • Carol X.

    Thank you so much! Found the WindowSWF folder thanks to your tutorial. I was having problems finding it to use FIVe3D

  • matt i.

    Great Tutorial, Michael! I wonder if you can help me solve a problem I’m having passing a string from AS3 to JSFL? I’ve added an Input Text Field to my FLA and added a new variable to my ARGs ‘text_field.text’ to pass some user defined string over to JSFL, but I get an error.

    runColorizeScript( cPicker_Fill.selectedColor, cPicker_OuterStroke.selectedColor.toString(), cPicker_InnerStroke.selectedColor.toString(), cPicker_Highlight.selectedColor.toString(), text_field.text);

    yields:

    The following JavaScript error(s) occurred:
    ReferenceError: is not defined (where is whatever was input into the text_field instance).

    Thank you for any advice!
    Matt I.

    • matt i.

      Whoops. The bracketed text was taken out of my comment. The error above should read like…

      The following JavaScript error(s) occurred:
      ReferenceError: SOME_TEXT_STRING is not defined (where SOME_TEXT_STRING is whatever was input into the text_field instance).

      Cheers. -m

      • http://michaeljameswilliams.com/ Michael James Williams
        Author

        Thanks, Matt :)

        Interesting error. It sounds like it’s treating SOME_TEXT_STRING as the name of a variable, rather than the contents of a string. I suspect you could fix this by adding single quotes around the string, like so:

        runColorizeScript( cPicker_Fill.selectedColor, cPicker_OuterStroke.selectedColor.toString(), cPicker_InnerStroke.selectedColor.toString(), cPicker_Highlight.selectedColor.toString(), "'" + text_field.text + "'");

        Make sense? Let me know whether that works.

  • Boboy

    Thank you so much for this piece of knowledge ! Very clear explanations !