<

[Last Call] Learn how to a build a cloud-first strategyRegister Now

x

Custom Cursors in Flash CS4

Published on
12,193 Points
6,193 Views
Last Modified:
Approved
There are times in your Flash CS4 application when you want more than a simple pointer or a hand, and it's hard to find an ideal walk-through to tell you what to do.  I spent a few days recently learning my way around making custom cursors in Flash, and here are the results.

There are some walkthroughs that tell you how to fake a custom cursor in Flash.  The general idea is to make the default cursor invisible, while attaching event listeners to its movement, and putting a DisplayObject on the stage that is always in the position that the mouse is.

Here is the code I came up with.  I've added liberal comments to explain what's going on in it:

package mypackage 
{ 
        import flash.display.MovieClip; 
        import flash.display.Stage; 
        import flash.events.Event; 
        import flash.events.MouseEvent; 
        import flash.ui.Mouse; 
         
        public class CustomMouse 
        { 
                // define a bunch of constants to be used when setting the cursor.  In this case, these are all 
                // abbreviations for constants I used in production code.
                public static const NORMAL:String = "NORMAL"; 
                public static const AE:String = "AE"; 
                public static const AL:String = "AL"; 
                public static const AP:String = "AP"; 
                public static const AR:String = "AR"; 
                public static const AT:String = "AT"; 
                public static const MD:String = "MD"; 
                public static const MV:String = "MV"; 
                public static const TA:String = "TA"; 
                public static const TH:String = "TH"; 
                public static const TI:String = "TI"; 
                public static const TL:String = "TL"; 
                public static const TR:String = "TR"; 
                public static const TT:String = "TT"; 
                public static const TV:String = "TV"; 
                public static const ZI:String = "ZI"; 
                public static const ZO:String = "ZO"; 
                 
                private var stage:Stage; 
                private var current:String; 
                private var cursor:MovieClip; 
                private var xOffset:int; 
                private var yOffset:int; 
                 
                public function CustomMouse( s:Stage ) 
                { 
                        this.stage = s; 
                        this.Cursor = NORMAL; 
                        this.xOffset = 0; 
                        this.yOffset = 0; 
                } 
                 
                public function set Cursor( val:String ):void 
                { 
                        this.current = val; 
                        if (this.cursor != null) 
                        { 
                               // for a normal cursor, just use the normal one, for better performance
                               // remove any event listeners you had added
                                this.stage.removeChild( this.cursor ); 
                                this.stage.removeEventListener( MouseEvent.MOUSE_MOVE, mouseMoved ); 
                                this.stage.removeEventListener( Event.MOUSE_LEAVE, mouseLeft ); 
                        } 
                         
                        if (this.current == NORMAL) 
                        { 
                                Mouse.show(); 
                                this.cursor = null; 
                        } 
                        else 
                        { 
                                // Hide the real mouse
                                Mouse.hide(); 
                                switch (this.current) 
                                { 
                                        // if needed, I can set the offsets in each case statement 
                                        case AE: 
                                                this.cursor = new cursorAE(); 
                                                break; 
                                        case AL: 
                                                this.cursor = new cursorAL(); 
                                                break; 
                                        case AP: 
                                                this.cursor = new cursorAP(); 
                                                break; 
                                        case AR: 
                                                this.cursor = new cursorAR(); 
                                                break; 
                                        case AT: 
                                                this.cursor = new cursorAT(); 
                                                break; 
                                        case MD: 
                                                this.cursor = new cursorMD(); 
                                                break; 
                                        case MV: 
                                                this.cursor = new cursorMV(); 
                                                break; 
                                        case TA: 
                                                this.cursor = new cursorTA(); 
                                                break; 
                                        case TH: 
                                                this.cursor = new cursorTH(); 
                                                break; 
                                        case TI: 
                                                this.cursor = new cursorTI(); 
                                                break; 
                                        case TL: 
                                                this.cursor = new cursorTL(); 
                                                break; 
                                        case TR: 
                                                this.cursor = new cursorTR(); 
                                                break; 
                                        case TT: 
                                                this.cursor = new cursorTT(); 
                                                break; 
                                        case TV: 
                                                this.cursor = new cursorTV(); 
                                                break; 
                                        case ZI: 
                                                this.cursor = new cursorZI(); 
                                                break; 
                                        case ZO: 
                                                this.cursor = new cursorZO(); 
                                                break; 
                                } 
                                // your DisplayObject is above the mouse cursor, and blocks click events, unless you
                                // turn off mouseEnabled and mouseChildren
                                this.cursor.mouseEnabled = false; 
                                this.cursor.mouseChildren = false; 
                                // cacheAsBitmap is supposed to give you slightly better performance
                                this.cursor.cacheAsBitmap = true;
                                // set initial position 
                                this.cursor.x = this.stage.mouseX + this.xOffset; 
                                this.cursor.y = this.stage.mouseY + this.yOffset; 
                                this.stage.addChild( this.cursor ); 
                                // catch MouseMove to follow the Mouse
                                this.stage.addEventListener( MouseEvent.MOUSE_MOVE, mouseMoved ); 
                                // catch MouseLeave so that you can make the custom mouse disappear.  If you don't do
                                // this, when the mouse leaves the window, you'll see the custom mouse on the screen
                                this.stage.addEventListener( Event.MOUSE_LEAVE, mouseLeft ); 
                        } 
                } 
                public function get Cursor():String 
                { 
                        return this.current; 
                } 
                 
                public function mouseMoved( e:MouseEvent ):void 
                { 
                        // no need to check that this.cursor is not null, because we remove the eventListener if it is.
                        this.cursor.visible = true; 
                        this.cursor.x = e.stageX + this.xOffset; 
                        this.cursor.y = e.stageY + this.yOffset; 
                        // this also is supposed to give you better performance.
                        e.updateAfterEvent(); 
                } 
                 
                public function mouseLeft( e:Event ):void 
                { 
                        this.cursor.visible = false; 
                } 
        } 
}

Open in new window


Usage of the above class is not very hard - when your application is added to the stage, create a new CustomMouse, passing stage as the parameter.  All the new cursors referred to in the switch statement are MovieClips within the library for the .FLA.  They could just as easily be Sprites; any DisplayObject will do.  In the case of my code, they were MovieClips that effectively did nothing more than load a .PNG.  

Any time you want to change the cursor, simply set it to one of the defined static constants.  Your basic code would be:
import mypackage.CustomMouse;
// ...
var customMouse:CustomMouse
// ...
function addedToStage( e:Event ):void
{
    // ...
    customMouse = new CustomMouse( stage );
    // ...
}

// ...
customMouse.cursor = CustomMouse.AE;

Open in new window


This works decently, but is somewhat slow because it only moves the mouse after catching an event, and only then responding to it.  In QA, we found that this doesn't work ideally for high performance applications.  I had one idea - since my application already had an OnEnterFrame event, I could change the cursor to update every frame, without having to wait for the MouseMove event.  The fastest way I could do this was to redefine the mouseMoved event, remove the listeners for it, and instead call the function from within the enterFrame event listener in my application:
                public function mouseMoved():void 
                { 
                        // no need to check that this.cursor is not null, because we remove the eventListener if it is.
                        this.cursor.visible = true; 
                        this.cursor.x = this.stage.mouseX + this.xOffset; 
                        this.cursor.y = this.stage.mouseX + this.yOffset; 
                } 

Open in new window


This seems like a good idea - and it may work for you - but in our tests, it was at least as bad as the original CustomMouse I had created.

Looking around online, I found references to Flex's CursorManager, which would do exactly what I need - it would let me change the standard Flash cursor, rather than hide it and follow it.  And to make a long posting short(er), I've found it to work a lot faster.  Here's the basic code:
package mypackage
{
	import mx.core.Application;
	import mx.managers.CursorManager;
	
	public class CustomMouse
	{
		public static const NORMAL:String = "NORMAL";
		public static const AE:String = "AE";
		public static const AL:String = "AL";
		public static const AP:String = "AP";
		public static const AR:String = "AR";
		public static const AT:String = "AT";
		public static const MD:String = "MD";
		public static const MV:String = "MV";
		public static const TA:String = "TA";
		public static const TH:String = "TH";
		public static const TI:String = "TI";
		public static const TL:String = "TL";
		public static const TR:String = "TR";
		public static const TT:String = "TT";
		public static const TV:String = "TV";
		public static const ZI:String = "ZI";
		public static const ZO:String = "ZO";
		
		[Embed(source="cursorImages/cursorAE.png")]
		private var CursorAE:Class;
		[Embed(source="cursorImages/cursorAL.png")]
		private var CursorAL:Class;
		[Embed(source="cursorImages/cursorAP.png")]
		private var CursorAP:Class;
		[Embed(source="cursorImages/cursorAR.png")]
		private var CursorAR:Class;
		[Embed(source="cursorImages/cursorAT.png")]
		private var CursorAT:Class;
		[Embed(source="cursorImages/cursorTA.png")]
		private var CursorTA:Class;
		[Embed(source="cursorImages/cursorMD.png")]
		private var CursorMD:Class;
		[Embed(source="cursorImages/cursorTH.png")]
		private var CursorTH:Class;
		[Embed(source="cursorImages/cursorTI.png")]
		private var CursorTI:Class;
		[Embed(source="cursorImages/cursorTL.png")]
		private var CursorTL:Class;
		[Embed(source="cursorImages/cursorTR.png")]
		private var CursorTR:Class;
		[Embed(source="cursorImages/cursorTT.png")]
		private var CursorTT:Class;
		[Embed(source="cursorImages/cursorMV.png")]
		private var CursorM:Class;
		[Embed(source="cursorImages/cursorTV.png")]
		private var CursorTV:Class;
		[Embed(source="cursorImages/cursorZI.png")]
		private var CursorZI:Class;
		[Embed(source="cursorImages/cursorZO.png")]
		private var CursorZO:Class;
		
		private var current:String;
		
		public function CustomMouse()
		{
			this.Cursor = NORMAL;
		}
		
		public function set Cursor( val:String ):void
		{
			this.current = val;
                                                // this is here to allow me to debug through the Flash IDE - the cursor won't
                                                // change, but I'll still have my functionality
			if (Application.application == null)
			{
				return;
			}
			CursorManager.removeCursor( CursorManager.currentCursorID );
			
			switch (this.current)
			{
				case AE:
					CursorManager.setCursor( CursorAE );
					break;
				case AL:
					CursorManager.setCursor( CursorAL );
					break;
				case AP:
					CursorManager.setCursor( CursorAP );
					break;
				case AR:
					CursorManager.setCursor( CursorAR );
					break;
				case AT:
					CursorManager.setCursor( CursorAT );
					break;
				case MD:
					CursorManager.setCursor( CursorMD );
					break;
				case MV:
					CursorManager.setCursor( CursorMV );
					break;
				case TA:
					CursorManager.setCursor( CursorTA );
					break;
				case TH:
					CursorManager.setCursor( CursorTH );
					break;
				case TI:
					CursorManager.setCursor( CursorTI );
					break;
				case TL:
					CursorManager.setCursor( CursorTL );
					break;
				case TR:
					CursorManager.setCursor( CursorTR );
					break;
				case TT:
					CursorManager.setCursor( CursorTT );
					break;
				case TV:
					CursorManager.setCursor( CursorTV );
					break;
				case ZI:
					CursorManager.setCursor( CursorZI );
					break;
				case ZO:
					CursorManager.setCursor( CursorZO );
					break;
			}
		}
		public function get Cursor():String
		{
			return this.current;
		}
	}
}

Open in new window


As you can see, it's a lot neater - no special caring for MovieClips, no catching going outside of the application window.  It's simple!  The hard part is integrating it.

First, you need some form of Flex.  This is not hard - you can download the Flex SDK from Adobe: http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK .  Once you have it, go into your .FLA file, and set your Publish settings.  From there, choose the Flash Tab, and then by Script: ActionScript 3.0, click Settings.  Go to the LibraryPath tab, and Browse to a SWC file.  Look wherever you installed your Flex SDK (you want version 3.2.0), then browse to frameworks\libs, and add framework.swc.  I don't think it's necessary, but I added flex.swc as well.

Good news!  Your code will now be at the point where it compiles!  If your code was good enough, it will even run, but it probably will not - especially not successfully.  When I had gotten this far, I was getting an annoying error message:
Error: No class registered for interface ‘mx.managers::ICursorManager’.
at mx.core::Singleton$/getInstance()
at mx.managers::CursorManager$/get impl()
In the end, the only solution I could find for this was to create a Flex application that loads the compiled .swf.  The mxml code for this is simple, and free - (all you need to do is download and setup FlashDevelop to go with your newly downloaded Flex sdk, from http://www.flashdevelop.org/community/viewforum.php?f=11).  Create a new mxml project, and simply put this in your Main.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
	<mx:SWFLoader id="loader2" source="MyFlashApp.swf"></mx:SWFLoader>
</mx:Application>

Open in new window


Things to look out for in your .FLA code - any reference you had in your .FLA action script that referred to the stage now needs to wait for an addedToStage event.  In the interests of making it more robust, you may need to check to see if your running via Flex or not.  This is very simple:
import mx.core.Application;
// ...
if (Application.application == null)
{
    // do not-loaded-in-Flex stuff
}

Open in new window


So, to summarize:
If you're not very worried about performance, simply use the top code segment.
If you want optimal performance, you need to use Flex's CursorManager.
     
Use the Flex SDK and add the appropriate SWC to your libraries.
   
Create a Flex application that does nothing but load your .swf (you can do it for free using FlashDevelop).
   
Make sure anything that referenced the stage in your old code now waits until addedToStage is fired.
   
If you want to keep your code robust, check anywhere before calling CursorManager that Application.application is not null.

It may be complicated, but it gives you a very high performance custom cursor.

If you have any suggestions or questions, feel free to post them, and I'll answer as well as I can.
0
Comment
Author:Carnou
0 Comments

Featured Post

How to Use the Help Bell

Need to boost the visibility of your question for solutions? Use the Experts Exchange Help Bell to confirm priority levels and contact subject-matter experts for question attention.  Check out this how-to article for more information.

Join & Write a Comment

The goal of the tutorial is to teach the user how to select the video input device. Make sure you have an input device that in connected and work and recognized by Adobe Flash Media Live Encoder and select it in the “video input” menu.
This Micro Tutorial will teach to how to utilize bit rate in Adobe Flash Media Live Encoder.
Suggested Courses
Course of the Month17 days, 14 hours left to enroll

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month