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!