1. Code
  2. JavaScript

AS3 101: OOP - Introducing Design Patterns

Scroll to top
34 min read
This post is part of a series called AS3 101.
AS3 101: OOP - Introduction to Interfaces
AS3 101: Events - Basix

After many months of learning how to program from the ground up, you're ready to put it all to use: we're going to build a simple drawing application. We will focus on Object-Oriented Programming techniques, specifically the use of interfaces. By setting up a few rules for our programming, we will make expanding the feature-set and debugging the project much easier.


Final Result Preview

You'll see that our little drawing app will be rather simplistic. Rest assured, though, that the internal logic just might make your brain explode. Don't worry; we'll take it one step at a time, as we always do. Here's a peak at what we're working towards in this tutorial:

While the result is simple, the underlying code is complex, and one of the things you'll hopefully learn along the way is that Object-Oriented Programming can keep you organized and keep your project maintainable.


Step 1: Create the Project

First things first: we need a home for our project. Start by creating a main folder for the entire project. Use the approach that works best for you. It could be as simple as creating a folder on your computer, using the OS, or using Flash Builder to create a new project for you.

It doesn't matter where this folder is created, so long as you can get to it when we need to save or open files.

I'll be calling this app "Drawr," so I'll be naming my folder accordingly. The name is of little consequence to the rest of the tutorial, so if you're feeling imaginative come up with your own name.

My project folder

Step 2: Create the Flash File

Open up Flash CS3 or higher, and choose File > New and then select the ActionScript 3.0 option. This will be our Flash file for the project.

The "New Document" WindowThe "New Document" WindowThe "New Document" Window

Save it to your project folder as "Drawr.fla" (or whatever name you feel is better).

Our Flash FileOur Flash FileOur Flash File

Step 3: Create a Classes Folder and the Source Path

We will organize our class files into packages, but for further organization, let's create a source path.

Create a folder in your project folder named "classes". All of our classes (and package folders) will go in here.

The classes folderThe classes folderThe classes folder

In your Flash document, choose File > Publish Settings (on the Mac, Option-Shift-F12; on Windows Alt-Shift-F12). Click on the "Flash" tab, and then the "Settings..." button next to "Script: ActionScript 3.0". Make sure the "Source path" tab is selected, and click on the "+" button. In the new entry, type "./classes".

If you'd like to know more about this procedure, please refer to the "Source Paths" step of this OOP tutorial.


Step 4: Create the Document Class

We will, of course, be embracing class files in this tutorial, so no code in the FLA! Create a document class as our main point of entry into the application's logic.

In the text editor of your choice, create a new file, and save it as "Drawr.as" in the classes folder. At this point, I recommend you follow my file- and class-naming instructions. It's easy to get confused if your names differ, or if you copy code from the tutorial that makes use of different class names than what you're using. I obviously can't police that, but consider it a friendly and helpful suggestion.

Our document classOur document classOur document class

Since this file is the document class, we have a few more sub-tasks to do. First, pop in some boilerplate code (if you used Flash Builder, almost all of this will be present already):

1
2
package {
3
    import flash.display.Sprite;
4
5
    public class Drawr extends Sprite {
6
        public function Drawr() {
7
            trace("Drawr has started.");
8
        }
9
    }
10
}

Now make sure that this document class is hooked up back in the Flash file. With nothing selected, open the Properties panel and enter "Drawr" in the "Class" field.

Setting the document class

To make sure our files are playing nicely so far, choose Control > Test Movie to run the movie. You should see the trace in the Output panel:

The Output panelThe Output panelThe Output panel

Step 5: Create a UI

If you like, you can just use the starter FLA in the download package for the visual assets (in the "drawr-start" folder). Or you can create your own UI. Here's what we need:

  1. Three "tool" buttons, a la Flash or Photoshop's tool buttons. Smallish in size, with some kind of icon to indicate the tool that will be activated when the button is selected. The three tools needed are:

    1. Rectangle button

      Rectangle Tool
    2. Oval button

      Oval Tool
    3. Brush button

      Brush Tool

    For example, they might be arranged in Flash like so:

    Tool buttons example

    You can grab these straight out the FLA, or even by downloading the above images, which are PNG-24 images with an alpha channel.

  2. A "canvas" area, which needs to primarily be an area of artwork on top of which we'll draw. Even if you make the area transparent, make sure the area has pixels filled it; we'll be use the MOUSE_DOWN event as a key ingredient to our drawing logic, and that event doesn't fire for an InteractiveObject if the mouse isn't over a pixel occupied by that InteractiveObject's pixels. Also, as a UI element, it's helpful to have some kind of area defined visually to know where the drawing can happen.

    Canvas exampleCanvas exampleCanvas example

All of these items should be MovieClips. Name these clips as so:

  • Rectangle Button: rectangle_mc
  • Oval Button: oval_mc
  • Brush Button: brush_mc
  • Canvas: canvas_mc

Step 6: Create a ToolBar Class

We're going to need a number of classes to control even this minimal UI. As hinted at earlier, we will follow best practices and place our classes in packages. We'll create the packages as we need them. In your classes folder, create a new folder called toolbar. We'll keep toolbar-related classes here (obviously).

We'll start with the Toolbar class. It's responsibility will be to handle the UI side of things with the tools. Create a file called Toolbar.as in the toolbar package.

The toolbar package and Toolbar classThe toolbar package and Toolbar classThe toolbar package and Toolbar class

Put the following code in it:

1
2
package toolbar {
3
4
    import flash.events.*;
5
    import flash.display.*;
6
7
    public class Toolbar extends EventDispatcher {
8
        private var _target:Sprite;
9
        public function Toolbar(target:Sprite) {
10
            _target = target;
11
            trace("Toolbar: " + _target);
12
        }
13
    }
14
}

Before we flesh this out, we need another class, the ToolbarButton. We'll do that in the next step. For now, though, you may notice that I'm choosing to compose a Sprite rather than extending one. This is my personal preference, as I find it more flexible in the long run. You may feel differently, but please see this previous tutorial in the OOP series for a lengthy discussion on composition. Regardless, I encourage you to follow my lead on this, as trying to accomplish the same thing but with inheritance instead of composition will undoubtedly cause problems that will be difficult to resolve by comparing your code to mine.

Let's make a quick test of the Toolbar class. First, in your FLA, select the three tool bar buttons and press F8 to group them into a Symbol. Name the symbol something like "Toolbar", but more importantly name the resulting instance "toolbar_mc"

Back in Drawr.as, let's add code to instantiate a Toolbar. Added lines are highlighted below:

1
2
package {
3
4
    import flash.display.Sprite;
5
6
    import toolbar.Toolbar;
7
8
    public class Drawr extends Sprite {
9
10
        private var _toolbar:Toolbar;
11
12
        public function Drawr() {
13
            _toolbar = new Toolbar(toolbar_mc);
14
        }
15
    }
16
}

And go ahead and test the movie; you should see a trace in the Output panel.

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

Step 7: Create the ToolbarButton Class

This class will be very simple, handling duties for a single button in the toolbar. Most importantly, it will carry with it a tool identifier so that a click on the button will ultimately result in the correct drawing behavior.

In the toolbar package, create a file named ToolbarButton.as. Place the following code in it:

1
2
package toolbar {
3
    import flash.display.Sprite;
4
    import flash.events.EventDispatcher;
5
    import flash.events.MouseEvent;
6
7
    public class ToolbarButton extends EventDispatcher {
8
        private var _target:Sprite;
9
        private var _toolType:String;
10
        public function ToolbarButton(target:Sprite, toolType:String) {
11
            _target = target;
12
            _toolType = toolType;
13
            _target.addEventListener(MouseEvent.ROLL_OVER, onOver);
14
            _target.addEventListener(MouseEvent.ROLL_OUT, onOut);
15
            _target.addEventListener(MouseEvent.CLICK, onClick);
16
        }
17
18
        private function onOver(e:MouseEvent):void {
19
            // Perform roll over effect.

20
        }
21
        private function onOut(e:MouseEvent):void {
22
            // Perform roll out effect.

23
        }
24
        private function onClick(e:MouseEvent):void {
25
            dispatchEvent(e);
26
        }
27
28
        public function get toolType():String {
29
            return _toolType;
30
        }
31
32
33
    }
34
}

All in all, pretty straight-forward. Take a Sprite and add some mouse interaction events to it. Also take a tool type String, and store it in a property while providing read-only access to it through a getter.

I'm not fussing over actual rollover and rollout effects for this tutorial. Feel free to put your own code in the appropriate methods.

We can now create the tool buttons from the Toolbar class. But we need a toolType to pass in. We could just make up string values, but it would be smarter to create some enumerated static constants (see my Quick Tip on static members). We can accomplish this by creating a very simple class.


Step 8: Create the ToolType Class

Make a new class file called ToolType.as in the toolbar package. It just needs a few static constants. Type in the following:

1
2
package toolbar {
3
    public class ToolType {
4
5
        public static const RECTANGLE:String = "rectangle";
6
        public static const OVAL:String = "oval";
7
        public static const BRUSH:String = "brush";
8
9
    }
10
}

We don't even need a constructor; the sole purpose of this class is to carry with it the various string values that we'll use to identify the various tool types.

Why not just put these constants on, say, the Toolbar class? I'm taking a page from Adobe's book on this one. This class shares a similar responsibility to classes like StageScaleMode or BlendMode (both in the flash.display package). They just declare public static constants that have String values of the valid values for the Stage.scaleMode and DisplayObject.blendMode properties, respectively.

One reason to consider this approach rather than putting the constants on the Toolbar class (or the Stage or DisplayObject classes) is that there's a good chance we'll want to access these values without actually caring about the Toolbar class. This lets us use the "type" class without worrying about a class with greater functionality. This simulates an enumerated type, which ActionScript does not have, and lets the class simply focus on enumeration of valid values. This enumeration can be easily used in multiple places, then.


Step 9: Create the ToolbarButton Objects

Now we have some supporting cast members, and we can let Toolbar take center stage. We'll fill it out to create ToolbarButton objects, passing tool type values and hooking up events. Open Toolbar.as and make the changes highlighted below (including the removal of the trace() line that was there before):

1
2
package toolbar {
3
4
    import flash.display.*;
5
    import flash.events.*;
6
7
    public class Toolbar extends EventDispatcher {
8
        private var _target:Sprite;
9
        public function Toolbar(target:Sprite) {
10
            _target = target;
11
12
            var rect:ToolbarButton = new ToolbarButton(_target.getChildByName("rectangle_mc") as Sprite, ToolType.RECTANGLE);
13
            var oval:ToolbarButton = new ToolbarButton(_target.getChildByName("oval_mc") as Sprite, ToolType.OVAL);
14
            var brush:ToolbarButton = new ToolbarButton(_target.getChildByName("brush_mc") as Sprite, ToolType.BRUSH);
15
16
            rect.addEventListener(MouseEvent.CLICK, onToolClick);
17
            oval.addEventListener(MouseEvent.CLICK, onToolClick);
18
            brush.addEventListener(MouseEvent.CLICK, onToolClick);
19
        }
20
21
        private function onToolClick(e:MouseEvent):void {
22
            trace("Tool click: " + e.target.toolType);
23
        }
24
    }
25
}

Still nothing too elaborate, but let's recap. First, we create three buttons. There's a pattern to how they get instantiated, so understanding one means you understand them all. If you recall, the ToolbarButton class takes two arguments to its constructor. The first is a Sprite, so we get the button symbol instances and pass them in. Note that we need to cast the result of getChildByName() as a Sprite because the result is a DisplayObject but we need to pass in a Sprite. The second argument is a String denoting the tool type, so here we use our ToolType enumeration class. So now we have three buttons, each associated with a Sprite and given a type.

Then we add CLICK events to each of the buttons. The handler for this simply (for now) traces out some information.

If you test your movie now, you should be able to click on the buttons and receive the traces.

The Output panel showing which buttons were clickedThe Output panel showing which buttons were clickedThe Output panel showing which buttons were clicked

Step 10: Dispatching Back to the Document Class

So far so good, but we can't stop here. That click event ultimately needs to get back to the Drawr class, so that it can in turn do something with that information. For this, we'll create a custom Event subclass to indicate a tool selection. It's a common practice to put a project's event classes into a single events package, and we'll do the same. Create a folder called events in your classes folder.

Then create a new text file and save it as ToolbarEvent.as.

The current project structureThe current project structureThe current project structure

The code will be as follows:

1
2
package events {
3
4
    import flash.events.Event;
5
6
    public class ToolbarEvent extends Event {
7
8
        public static const SELECT:String = "select";
9
10
        private var _toolType = toolType;
11
12
        public function ToolbarEvent(type:String, toolType:String, bubbles:Boolean=false, cancelable:Boolean=false) {
13
            _toolType = toolType;
14
            super(type, bubbles, cancelable);
15
        }
16
17
        public function toString():String {
18
            return formatToString("ToolbarEvent", "type", "toolType");
19
        }
20
21
        public function clone():Event {
22
            return new ToolbarEvent(type, toolType, bubbles, cancelable);
23
        }
24
25
        public function get toolType():String {
26
            return _toolType;
27
        }
28
29
    }
30
}

If you worked through the previous tutorial where we built an image viewer, subclassing Event to create a custom event shouldn't be anything new. Our main purpose for doing this is to create an event with a toolType property. It's also handy to store the event type name in a static constant here, as well.

With this event class created, move back to Toolbar.as and import the ToolbarEvent class:

1
2
package toolbar {
3
4
    import events.ToolbarEvent;

And modify the onToolClick method to remove the trace and dispatch a ToolbarEvent:

1
2
private function onToolClick(e:MouseEvent):void {
3
    dispatchEvent(new ToolbarEvent(ToolbarEvent.SELECT, e.target.toolType));
4
}

The final task in this step is to listen to this event from the Drawr class. In that class, import the ToolbarEvent class and add an event listener to the Toolbar object:

1
2
package {
3
4
    import events.ToolbarEvent;
5
6
    import flash.display.Sprite;
7
8
    import toolbar.Toolbar;
9
10
    public class Drawr extends Sprite {
11
12
        private var _toolbar:Toolbar;
13
14
        public function Drawr() {
15
            _toolbar = new Toolbar(toolbar_mc);
16
            _toolbar.addEventListener(ToolbarEvent.SELECT, onToolbarSelect);
17
        }
18
19
        private function onToolbarSelect(e:ToolbarEvent):void {
20
            trace("Toolbar select: " + e.toolType);
21
        }
22
23
    }
24
}

This will only slightly modify the behavior for right now; but if you're still getting the trace when you test the movie, then you're successfully dispatching from the Toolbar object to the document class. We'll come back to this in just a bit.


Step 11: Create A Canvas Class

We'll move on to the other key piece of the UI, the area where we will draw the artwork. Create another package (folder) called canvas in the classes folder.

Next, create a new ActionScript file and save it as Canvas.as in the newly-created "canvas" folder.

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

Enter the following code:

1
2
package canvas {
3
4
    import flash.display.*;
5
    import flash.events.*;
6
    import flash.geom.*;
7
8
    public class Canvas {
9
10
        private var _target:Sprite;
11
12
        public function Canvas(target:Sprite) {
13
            _target = target;
14
            _target.addEventListener(MouseEvent.MOUSE_DOWN, onCanvasDown);
15
        }
16
17
        private function onCanvasDown(me:MouseEvent):void {
18
            trace("onCanvasDown");
19
            _target.stage.addEventListener(MouseEvent.MOUSE_MOVE, onCanvasMove);
20
            _target.stage.addEventListener(MouseEvent.MOUSE_UP, onCanvasUp);
21
        }
22
23
        private function onCanvasMove(me:MouseEvent):void {
24
            var newX:Number = _target.mouseX;
25
            var newY:Number = _target.mouseY;
26
            trace("onCanvasMove: " + newX + ", " + newY);
27
        }
28
29
        private function onCanvasUp(me:MouseEvent):void {
30
            trace("onCanvasUp");
31
            _target.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onCanvasMove);
32
            _target.stage.removeEventListener(MouseEvent.MOUSE_UP, onCanvasUp);
33
        }
34
    }
35
}

We'll be working in this class quite a bit over the course of the tutorial, but for now, the idea is to get some key functionality going. The drawing logic will take place on the canvas, but only after the user clicks the mouse down on the canvas. So the first thing we set up is a MOUSE_DOWN listener on the canvas target (notice that we're using composition again, not inheritance. This object will have a Sprite).

In that MOUSE_DOWN listener, we add two more events: MOUSE_MOVE and MOUSE_UP. We don't want these events to fire at any old time, otherwise we might be drawing just because the mouse moved, whether or not a click on the canvas occurred. So we add these events after the MOUSE_DOWN occurred.

In the MOUSE_MOVE listener, for now we're just getting the position of the mouse relative to the canvas Sprite, and tracing it out. We'll be doing a lot more here shortly.

Finally, in the MOUSE_UP handler, the important thing to do, considering that our drawing is done at this point, is to remove those MOUSE_MOVE and MOUSE_UP listeners, leaving just the MOUSE_DOWN listener, which can start the process over again.

This is good stuff, but there's nothing to test yet. We need to create a Canvas object first. That's next.


Step 12: Create the Canvas Object

Hop back to the Drawr class, and add the highlighted lines in the following code:

1
2
package {
3
4
    import canvas.Canvas;
5
6
    import events.ToolbarEvent;
7
8
    import flash.display.Sprite;
9
10
    import toolbar.Toolbar;
11
12
    public class Drawr extends Sprite {
13
14
        private var _canvas:Canvas;
15
        private var _toolbar:Toolbar;
16
17
        public function Drawr() {
18
            _canvas = new Canvas(canvas_mc);
19
            _toolbar = new Toolbar(toolbar_mc);
20
            _toolbar.addEventListener(ToolbarEvent.SELECT, onToolbarSelect);
21
        }
22
23
        private function onToolbarSelect(e:ToolbarEvent):void {
24
            trace("Toolbar select: " + e.toolType);
25
        }
26
27
    }
28
}

We're just importing the class, creating a property, and instantiating a Canvas object into that property; should be pretty straight-forward so far.

Go ahead and test the movie. Once running, click and drag in the canvas area. You should get a whole bunch-o-traces.

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

Step 13: Thinking About Tools

We have our basic Canvas, now how do we draw into it? Let's think about the general mechanics of drawing, say, a rectangle.

Let's divide the phases of drawing a rectangle into three distinct phases. First, there is the start phase. It's not until this phase happens that we actually begin drawing (you could say that there is a phase before the start phase, called the not doin' nuthin' phase). The second phase is the drawing phase; here, we're moving the mouse around to draw temporary rectangles as we settle on the rectangle we actually want. Finally, we have the commit phase. This is where we finally choose the rectangle that we want.

The perceptive among you will have noticed that these three phases align nicely with the three events that we've set up in the Canvas class: the MOUSE_DOWN event coincides with the start phase, and in that event handler we need to perform any set up we need to start drawing. The drawing phase is matched to the MOUSE_MOVE event, where we move the mouse around and try to get the size of our rectangle right. And the MOUSE_UP event corresponds to the commit phase; once the mouse button goes up, we're done drawing and we have our rectangle.

In rather general terms, then, here's what we need to do in each of those phases/events.

  1. start: As one corner of the rectangle is always anchored to the mouse position at this phase, we need to capture the mouse position in the MOUSE_DOWN event and cache it in a variable for later use.
  2. drawing: The opposing corner of the rectangle follows the mouse around during this phase. We need to continually update the drawn rectangle based on the continually updating location. It's important to note that that means we're actually drawing shapes in this phase. We'll be using ActionScript's drawing API to accomplish this.
  3. commit: There's actually not too much to do here, other than clean up our event handlers (as already mentioned in a few steps ago in regards to the Canvas class). The shape is already drawn from the drawing phase, we just need to stop drawing and leave the shape be. However, you may note that Flash's drawing tools have a "lightweight" outline drawn during the drawing phase, which then get properly rendered with fills and strokes once committed. So, it's conceivable that you might want to perform a simple drawing routine in the drawing phase and draw it "for real" in the commit phase. We won't be doing that, but I thought I'd mention the possibility.

Now, think about an oval tool. What kinds of drawing phases would we have with drawing ovals? If you said "exactly the same kind," then you're some kind of wizard, because that's correct. The above three steps could just as easily apply to drawing ovals, or for that matter, lines, or most any geometric shape like stars or regular polygons. The only real difference will be the logic for the drawing of the shape, during the drawing phase.


Step 14: Can We Just Make A Big if Statement?

...in the MOUSE_MOVE handler and draw different shapes accordingly? Sure, we could. But that's not a terribly object-oriented approach to the problem at hand.

What if we had different objects for each tool? A RectangleTool class, an OvalTool class, etc? Then we can encapsulate the logic of drawing a specific tool into a single class, and keep Canvas related logic separated. The Canvas object can retain a property of the "current tool object" and defer the drawing logic to that object. The current object can get updated whenever it's appropriate — say, when you click on one tool or another. This strategy sounds appealing (at least, it does to me, and I'm hoping that, by now, it does to you as well).

But this creates a different problem. If there is a single "current tool object" property in the Canvas object, how can we possibly put various different datatypes into it; that is, how do to we type the property so that it can receive both the RectangleTool and the OvalTool objects as values? Not to mention other tools that may eventually get built?

The answer is interfaces, and despite my dramatic build up, that shouldn't be surprising given the topic of this tutorial. If we can define an interface that more generically declares methods that can be called by the Canvas class, then we can use the interface as the datatype for the "current tool object" in Canvas, and make sure each actual tool object implements the interface.

Sound good? Of course it does (if it doesn't, try re-reading this step. If it still doesn't make sense after that, then try taking a leap of faith and forge ahead with the tutorial; it will probably make more sense as we build it out bit-by-bit).


Step 15: Creating the Interface

Create another package in the project, this time called tools. This will be conceptually different than classes in the toolbar package: toolbar classes are UI elements; tools classes will involve logic surrounding drawing things on the canvas.

Create a new text file and save it to the tools folder as ITool.as.

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

This will be our interface file, and so it won't be too lengthy:

1
2
package tools {
3
4
    import flash.display.DisplayObject;
5
6
    public interface ITool {
7
8
        function mouseDown(x:Number, y:Number, fillColor:uint):void;
9
        function mouseMove(x:Number, y:Number):void;
10
        function mouseUp(x:Number, y:Number):void;
11
12
        function get art():DisplayObject;
13
14
    }
15
}

We will utilize those three phases in the first three methods: mouseDown, mouseMove, and mouseUp. Each will expect the mouse's x and y position. The mouseDown method will also get passed the fill color to use.

There is an extra method in here that we haven't really discussed yet, but I'm adding it in here to avoid having to add it in later. We'll get to it momentarily, but a quick description of it is that the tool actually creates the artwork, and the Canvas will retrieve that artwork as a "layer" for it to display.


Step 16: Implementing the Interface with the Rectangle Tool

Let's put this interface to work. Create a new class called RectangleTool in the tools package (that is, create a "Rectangle.as" file in the "tools" folder).

We can stub in a basic class. Because we'll be implementing ITool, we can make sure we get the required methods in place, even if they're empty.

1
2
package tools {
3
4
    import flash.display.*;
5
6
    public class RectangleTool implements ITool {
7
8
        public function RectangleTool() {
9
        }
10
11
        public function mouseDown(x:Number, y:Number, fillColor:uint):void {
12
            trace("mouseDown: " + x + ", " + y + ", " + fillColor.toString(16));
13
        }
14
15
        public function mouseMove(x:Number, y:Number):void {
16
            trace("mouseMove: " + x + ", " + y);
17
        }
18
19
        public function mouseUp(x:Number, y:Number):void{
20
            trace("mouseUp: " + x + ", " + y);
21
        }
22
23
        public function get art():DisplayObject {
24
            return new Shape();
25
        }
26
    }
27
}

While we're not really doing anything in this class, we've at least fulfilled the contract of ITool and created the methods required. We'll get to a more practical implementation in a moment, but our next task will be to put this RectangleTool to use.


Step 17: Giving the Canvas an ITool

Because the Canvas receives the mouse events, it's the object that needs to tell the RectangleTool about those three phases. And because the Drawr object is the one that's receiving events from the Toolbar object when a tool is selected, we can use the Drawr class to tell the Canvas which tool to use.

For this to happen, the Canvas needs a public property (or a private property with a public setter and getter) that holds a reference to the RectangleTool. It then needs set by the Drawr when a tool is selected.

In Canvas.as, add the following highlighted lines. Note that I've also removed the three traces that were in this file previously.

1
2
package canvas {
3
4
    import flash.display.*;
5
    import flash.events.*;
6
    import flash.geom.*;
7
8
    import tools.ITool;
9
10
    public class Canvas {
11
12
        private var _target:Sprite;
13
        private var _currentTool:ITool
14
15
        public function Canvas(target:Sprite) {
16
            _target = target;
17
            _target.addEventListener(MouseEvent.MOUSE_DOWN, onCanvasDown);
18
        }
19
20
        private function onCanvasDown(me:MouseEvent):void {
21
            if (!_currentTool) return;
22
            _target.stage.addEventListener(MouseEvent.MOUSE_MOVE, onCanvasMove);
23
            _target.stage.addEventListener(MouseEvent.MOUSE_UP, onCanvasUp);
24
            _currentTool.mouseDown(_target.mouseX, _target.mouseY, 0xFF0000);
25
        }
26
27
        private function onCanvasMove(me:MouseEvent):void {
28
            var newX:Number = _target.mouseX;
29
            var newY:Number = _target.mouseY;
30
            _currentTool.mouseMove(newX, newY);
31
        }
32
33
        private function onCanvasUp(me:MouseEvent):void {
34
            _target.stage.removeEventListener(MouseEvent.MOUSE_MOVE, onCanvasMove);
35
            _target.stage.removeEventListener(MouseEvent.MOUSE_UP, onCanvasUp);
36
            _currentTool.mouseUp(_target.mouseX, _target.mouseY);
37
        }
38
39
        public function get currentTool():ITool {
40
            return _currentTool;
41
        }
42
43
        public function set currentTool(value:ITool):void {
44
            _currentTool = value;
45
        }
46
47
48
    }
49
}

All of these changes center around adding a new _currentTool property. The import, property declaration, and setter and getter shouldn't require any explanation. The other lines involve using the _currentTool property. And what's going on here shouldn't be a big surprise; we're just calling the three "phase" methods at the appropriate times according to user interaction. As a side note, we're also checking — on line 20 — for the existence of a _currentTool object before proceeding with all of this. If it doesn't exist, don't add any event listeners or call any methods on the _currentTool property, because we'd get errors if we did.

But we can't test just yet; we need that _currentTool property to be set before we see anything new. So, in Drawr.as, add this bit of code to the onToolbarSelect method:

1
2
private function onToolbarSelect(e:ToolbarEvent):void {
3
    trace("Toolbar select: " + e.toolType);
4
    _canvas.currentTool = new RectangleTool();
5
}

And you'll want to import not just the RectangleTool class, but pretty much anything in the tools package.

1
2
import tools.*;

Now go ahead and test the movie. Click on the rectangle tool button (or any of the tools, to be fair), and then "draw" in the canvas area. You won't see any rectangles show up, but you'll see the traces from the RectangleTool class show up.

Traces from the RectangleToolTraces from the RectangleToolTraces from the RectangleTool

To recap what's happening, we're supplying the Canvas object with an ITool object. It happens to be the RectangleTool right now. So when the mouse events occur, and Canvas is responding to them, it also asks the ITool object to do its thing, by calling methods on them. This will get more fleshed out as we go along, so if it's not making sense right now, try to follow the sequence of events through the lines of code in the various classes. If it is making sense, but just seems like too much work for too little result, just hang in there. The grand scheme has yet to be revealed.


Step 18: Drawing a Rectangle

Open up RectangleTool.as again. Remove the traces, and replace them with the following code:

1
2
package tools {
3
4
    import flash.display.*;
5
    import flash.geom.Point;
6
7
    public class RectangleTool implements ITool {
8
9
        private var _art:Shape;
10
        private var _fillColor:uint;
11
12
        public function RectangleTool() {
13
        }
14
15
        public function mouseDown(x:Number, y:Number, fillColor:uint):void {
16
            _art = new Shape();
17
            _art.x = x;
18
            _art.y = y;
19
            _fillColor = fillColor;
20
        }
21
22
        public function mouseMove(x:Number, y:Number):void {
23
            _art.graphics.clear();
24
            _art.graphics.beginFill(_fillColor, 1);
25
            _art.graphics.drawRect(0, 0, x-_art.x, y-_art.y);
26
        }
27
28
        public function mouseUp(x:Number, y:Number):void{
29
        }
30
31
        public function get art():DisplayObject {
32
            return _art;
33
        }
34
    }
35
}

And there is one more task to do before we'll actually see something. We need to get the art object out of the RectangleTool and into the Canvas. In Canvas.as, add one line of code to the onCanvasDown method:

1
2
private function onCanvasDown(me:MouseEvent):void {
3
    if (!_currentTool) return;
4
    _target.stage.addEventListener(MouseEvent.MOUSE_MOVE, onCanvasMove);
5
    _target.stage.addEventListener(MouseEvent.MOUSE_UP, onCanvasUp);
6
    _currentTool.mouseDown(_target.mouseX, _target.mouseY, 0xFF0000);
7
    _target.addChild(_currentTool.art);
8
}

In our app's current condition, once we set the Canvas' _currentTool to a RectangleTool object, this is the sequence of events:

  1. Nothing happens until a MOUSE_DOWN on the Canvas.
  2. At this point, we tell the RectangleTool to execute its mouseDown method.
  3. The RectangleTool then creates a Shape object, sets its position, and also stores the fillColor for later use.
  4. The Canvas object is still executing the MOUSE_DOWN handler; it then requests the art object from the RectangleTool
    object and adds it to its display list.
  5. We're done for now, until a MOUSE_MOVE event happens.
  6. In the MOUSE_MOVE handler, the Canvas forwards along the current mouse coordinates to the RectangleTool.
  7. The RectangleTool then uses this new location, along with the original location of the mouse at MOUSE_DOWN (stored in
    _art.x and _art.y), to draw the rectangle.
  8. The MOUSE_UP event eventually fires, at which point we stop drawing and leave the drawn rectangle as is.
  9. If you then click again, the MOUSE_DOWN event fires again, repeating Steps 2-8, resulting in another rectangle.

And we can draw rectangles!

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

We still haven't seen the advantage of the ITool interface yet, but we're getting there soon.


Step 19: Drawing an Oval

Let's take it to the next level and introduce a second drawing tool: the Oval.

In the tools package, create a new class called OvalTool and add the following code:

1
2
package tools {
3
4
    import flash.display.*;
5
    import flash.geom.Point;
6
7
    public class OvalTool implements ITool {
8
9
        private var _art:Shape;
10
        private var _fillColor:uint;
11
12
        public function OvalTool() {
13
        }
14
15
        public function mouseDown(x:Number, y:Number, fillColor:uint):void {
16
            _art = new Shape();
17
            _art.x = x;
18
            _art.y = y;
19
            _fillColor = fillColor;
20
        }
21
22
        public function mouseMove(x:Number, y:Number):void {
23
            _art.graphics.clear();
24
            _art.graphics.beginFill(_fillColor, 1);
25
            _art.graphics.drawEllipse(0, 0, x-_art.x, y-_art.y);
26
        }
27
28
        public function mouseUp(x:Number, y:Number):void{
29
        }
30
31
        public function get art():DisplayObject {
32
            return _art;
33
        }
34
    }
35
}

You may notice that this class bears a strong resemblance to the RectangleTool class. The logic is near identical; aside from having to rename the class and constructor to OvalTool, the only difference is in mouseMove, where, instead of calling _art.graphics.drawRect, we call _art.graphics.drawEllipse.

If you'd like to test it out right now, you can hop over to the Drawr class and in the onToolbarSelect method, set the _canvas.currentTool to a new OvalTool() instead of a new RectangleTool(). You could alternatively wait until you get through the next step, because we'll be focusing on properly setting the currentTool next.


Step 20: Translating Between Toolbar Buttons and ITool Types

Of course, what we have is far from ideal. We have three tool buttons, we need three tools. Well, we have two tools now, so it's time to work out how to select those tools appropriately.

Drawr's responsibility will be to translate between the tool selection event and the actual ITool we need. Open up Drawr.as and locate the onToolbarSelect method. Remove the code currently there and replace it with this:

1
2
private function onToolbarSelect(e:ToolbarEvent):void {
3
    switch (e.toolType) {
4
        case ToolType.RECTANGLE:
5
            _canvas.currentTool = new RectangleTool();
6
            break;
7
        case ToolType.OVAL:
8
            _canvas.currentTool = new OvalTool();
9
            break;
10
        default:
11
            _canvas.currentTool = null;
12
    } 
13
}

We also need to import the ToolType class. Add the following import line towards the top of the file, with the rest of the imports:

1
2
import toolbar.ToolType;

And now for the exciting test. Go ahead and run the movie. Click the Rectangle tool, and draw. You should get a rectangle. Then click the Oval tool, and draw again. It should be an oval. Well done!

Flash AS3 OOP tutorialFlash AS3 OOP tutorialFlash AS3 OOP tutorial

Step 21: Creating a Brush Tool

There is one more tool to create (at least, for the purposes of this tutorial). This one will be quite different. If you happened to think that the RectangleTool and OvalTool classes were so similar that perhaps we were going about this all wrong, then our next tool class will be a big enough departure to convince you that separate classes for each tool are the way to go.

However, even though the underlying logic will be radically different, we still have the same three-phase approach to the task of drawing. Drawing a brush stroke still doesn't start until MOUSE_DOWN, and then gets updates with each MOUSE_MOVE, and terminates with MOUSE_UP. It's just what we actually do in those three phases will be different.

This is perfect for our interface. We have an interface that defines those phases, but implementations that does unique things with the phases.

With that in mind, create a BrushTool.as file in the tools folder, and add the following code:

1
2
package tools {
3
    import flash.display.*;
4
    import flash.geom.*;
5
6
    public class BrushTool implements ITool {
7
8
        private var _bitmap:Bitmap;
9
        private var _bmd:BitmapData;
10
        private var _brushStroke:Shape;
11
12
        public function BrushTool() {
13
            _bmd = new BitmapData(500, 500, true, 0x00000000);
14
            _bitmap = new Bitmap(_bmd);
15
        }
16
17
        public function mouseDown(x:Number, y:Number, fillColor:uint):void {
18
            _brushStroke = new Shape();
19
            var gradBox:Matrix = new Matrix();
20
            gradBox.createGradientBox(20, 20, 0, 0, 0);
21
            _brushStroke.graphics.beginGradientFill(GradientType.RADIAL, [fillColor, fillColor], [1, 0], [127,255], gradBox)
22
            _brushStroke.graphics.drawCircle(10, 10, 10);
23
24
            var m:Matrix = new Matrix();
25
            m.translate(x-10, y-10)
26
            _bmd.draw(_brushStroke, m);
27
        }
28
29
        public function mouseMove(x:Number, y:Number):void {
30
            var m:Matrix = new Matrix();
31
            m.translate(x-10, y-10)
32
            _bmd.draw(_brushStroke, m);
33
        }
34
35
        public function mouseUp(x:Number, y:Number):void {
36
        }
37
38
        public function get art():DisplayObject {
39
            return _bitmap;
40
        }
41
    }
42
}

As you can see, that code is quite different. I won't spend a lot of time explaining it, as dealing with BitmapData isn't the focus of this tutorial (although I am planning an introduction to BitmapData in the near future, so if you find this intriguing, stay tuned). The general premise, however, is that we have a Bitmap object as our art object. Rather than drawing with the drawing API like we did with the geometric rectangles and ovals, we'll work with pixels for the brush.

The bitmap logic involves cloning the pixels of a "brush tip" (called _brushStroke) into the BitmapData object. The _brushStroke is just a circle with a gradient fill, that is solid in the center and transitions to completely transparent at the edges, like a feathered brush in Photoshop. By copying this art over and over into the bitmap's pixels, we get the impression of a continuous line.

The illusion isn't as nice as Photoshop's is, of course; if you move the mouse fast you'll see the individual brush strokes. This can be addressed with more logic and math, but to keep things from getting out of control, we'll stick with this more rudimentary implementation.

Now, to make this work, we need to expand the scope of the onToolbarSelect method in Drawr. Add the highlighted lines:

1
2
private function onToolbarSelect(e:ToolbarEvent):void {
3
    switch (e.toolType) {
4
        case ToolType.RECTANGLE:
5
            _canvas.currentTool = new RectangleTool();
6
            break;
7
        case ToolType.OVAL:
8
            _canvas.currentTool = new OvalTool();
9
            break;
10
        case ToolType.BRUSH:
11
            _canvas.currentTool = new BrushTool();
12
            break;
13
        default:
14
            _canvas.currentTool = null;
15
    } 
16
}

Test it out; you should have a brush tool not unlike the brush tool you know from Photoshop.

The brush tool a-brushingThe brush tool a-brushingThe brush tool a-brushing

Step 22: Choosing a Fill Color

So far, we've made a point of specifying a fill color, but have hard-coded it to that bold red. Allowing the user to change the colour is relatively simple, and before we close down for the day we'll make it happen.

In your FLA, open up the components panel by choosing the Window > Components menu item (or by pressing Command/Control-F7).

The components panelThe components panelThe components panel

Locate the ColorPicker component, and drag it out on to the stage, underneath the toolbar (location isn't too important so long as it's not on top of the canvas area).

Placing the color picker component on the stage

Give the ColorPicker an instance name of fillColorPicker.

Open up the Canvas class and add a _fillColor property and matching public set/get functions.

1
2
private var _fillColor:uint;
3
4
[firstline="48"]
5
public function set fillColor(color:uint):void {
6
    _fillColor = color;
7
}
8
public function get fillColor():uint {
9
    return _fillColor;
10
}

Change the hard-coded 0xFF0000 to use the _fillColor property instead:

1
2
_currentTool.mouseDown(_target.mouseX, _target.mouseY, _fillColor);

And finally, add the following code to the Drawr class:

1
2
package {
3
4
    import canvas.Canvas;
5
6
    import events.ToolbarEvent;
7
8
    import flash.display.Sprite;
9
    import flash.events.Event;
10
11
    import toolbar.Toolbar;
12
    import toolbar.ToolType;
13
14
    import tools.*;
15
16
    public class Drawr extends Sprite {
17
18
        private var _canvas:Canvas;
19
        private var _toolbar:Toolbar;
20
21
        public function Drawr() {
22
            _canvas = new Canvas(canvas_mc);
23
            _toolbar = new Toolbar(toolbar_mc);
24
            _toolbar.addEventListener(ToolbarEvent.SELECT, onToolbarSelect);
25
            fillColorPicker.addEventListener(Event.CHANGE, onFillColorChange);
26
        }
27
28
        private function onToolbarSelect(e:ToolbarEvent):void {
29
            switch (e.toolType) {
30
                case ToolType.RECTANGLE:
31
                    _canvas.currentTool = new RectangleTool();
32
                    break;
33
                case ToolType.OVAL:
34
                    _canvas.currentTool = new OvalTool();
35
                    break;
36
                case ToolType.BRUSH:
37
                    _canvas.currentTool = new BrushTool();
38
                    break;
39
                default:
40
                    _canvas.currentTool = null;
41
            } 
42
        }
43
44
        private function onFillColorChange(e:Event):void {
45
            _canvas.fillColor = fillColorPicker.selectedColor;
46
        }
47
48
    }
49
}

If you test the movie now, you should be able to change the fill color of the current tool by using the component.

Amazing TechnicolorAmazing TechnicolorAmazing Technicolor

Step 23: Reviewing the System

In retrospect, the system probably seems pretty sleek. Consider what it would take to add a new tool. Of course, there are the the UI considerations: you'll need a new button, you'll need to update Toolbar to accommodate it, and you'll need to add a new enumeration to ToolType.

In terms of implementing the tool, though, you have one clear-cut task: implement ITool. If you did it faithfully, you should be good to go. Yes, you'll need to update onToolbarSelect to create the new tool at the right time. The the arguably trickier logic of actually drawing will get encapsulated into its own class. You don't need to worry about hooking up mouse events; the "phase" methods get called automatically. You just need to take the given information and draw with it.

Compare that to how it might go down without the interface; if we had implemented the big if statement we discussed above. Well, obviously, that big if statement will get bigger, and probably harder to maintain. It's likely you'll try to share variables or forget that a variable set earlier needs to remain set at the value before you change it for the new tool. There's any number of things that could happen to create a mess at this point. This particular technique of separating the raw interaction logic from the individual drawing logic should help protect against introducing errors alongside new features.

It's not necessarily any less work or fewer lines of code to use interfaces versus not, but the focus of the work is sharper, meaning you can execute the work more confidently in less time. We also have clearly defined responsibilities for each object, making bugs easier to locate.


Step 24: That Last Step is a Doozy

Not only do we have the advantages outlined in the previous step, but you have now been furtively introduced to Design Patterns. Now, that's a terrible thing to bring up in the last step of a tutorial, because it's a subject that's amazingly (and rewardingly) deep. To give a nickel tour of design patterns, they are solutions to common problems in programming, specifically using Object-Oriented techniques. They are typically regarded as "the next thing" to master once you become comfortable with OOP on more general level.

For example, a common problem is the need to use different algorithms. The Strategy pattern is a generic approach to a solution. In our drawing application, we need different drawing algorithms based on the selected tool. The Strategy pattern tells us that we can treat each individual algorithm as its own class, and all algorithm classes should implement the same interface -- just as we did with the three tools' classes and the ITool interface. The "Context" class (the consumer or client of the algorithms) then uses one algorithm class or another to actually complete its own task.

The beauty of the Strategy pattern, though, is the ease with which the algorithm can be swapped out with a different one, as illustrated with the selection of tools in our drawing app.

The applications of this particular pattern are endless, and that's just one pattern. There are plenty of other design patterns that are just as useful. This subject is, naturally, worth several tutorials, and explaining it all in a few paragraphs is really an injustice. I just wanted to mention it, because I rather intentionally designed the drawing app around not only the use of interfaces, but the Strategy pattern. I hope the topic is intriguing enough to warrant further exploration on your own. I just wanted to plant a seed.


Conclusion

And that wraps up our discussion on interfaces! They are one of the more obtuse concepts in Object-Oriented Programming, and nobody will fault you for not mastering them right away. But I hope that, through this more practical application, you've come to grips with the value of interfaces, even if just a little bit. Thanks for sticking it out and reading this tutorial.

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.