Showing posts with label flash. Show all posts
Showing posts with label flash. Show all posts

Thursday, June 3, 2010

Video: 3D Distortion

3D Distortion in Flex and Flash, the next episode in the gripping and suspenseful series CodeDependent, is now available from Adobe TV.

This episode shows how and why you might see some distortion with 3D objects in Flash and Flex (hint: it's about bitmap scaling).

Here's the video:

Here is the demo application:

And here is the source code.

Here's where you can find CodeDependent on iTunes.

And here's where you can find CodeDependent on YouTube.

Enjoy.

Friday, April 2, 2010

Video: Using Flexy in a Flash Project

Using Libraries in Flash Pro, the next episode in the gripping and suspenseful series CodeDependent, is now available from Adobe TV.

This espisode is the stunning conclusion to the previous nail-biting cliff-hanger episode, Creating a Reusable Animation Library, in which we saw how to use Flash Pro to create Flexy, a small and Flex-free version of the timing engine underlying Flex effects. In this episode, we see how to use Flash Pro to link against Flexy and use it to create a simple ActionScript-based animation, writing code that looks very close to what we would write in Flex 4 for doing the same animation.

Once again, my thanks go to Trevor McCauley for helping me figure this out. Trevor's on the Flash player team and is very helpful is figuring out all kinds of things about how Flash works, including this bit about Flash authoring. Check out his blog at senocular.com.

I should probably spend more time and words on the Flexy library itself, but that will have to be for another day and another blog entry. But check out the previous episode for more information, and use your vast knowledge about the Flex Animation class and the related classes that you've accumulated so far from my blog and videos. It all carries over to Flexy, because Flexy is basically a subset of what I've been talking about with Flex 4 effects. It's just the timing engine part of effects (the 'Tween' engine, in Flash parlance), plus a helper class to assist in setting the animated properties on the target objects.

But for now, I'll just stick with the content of this show.

Here's the video:

Here is the demo application:

Here is the demo code. This is the Flash Pro project (FlexyTest.fla), which links to the Flexy library itself (Flexy.swc).

Here's where you can find CodeDependent on iTunes.

And here's where you can find CodeDependent on YouTube.

Enjoy.

Friday, March 12, 2010

Video: Creating a Reusable Animation Library

Creating a Reusable Flash Animation Library, the next episode in the gripping and suspenseful series CodeDependent, is now available from Adobe TV.

This episode is a bit of a departure from my normal shows. Usually, I show graphics and animation techniques in Flex and Flex 4. This time, I talk about Flash Pro, the authoring tool for Flash (not Flex) applications. In particular, I show how to create a reusable library in Flash Pro that you can then link to and use in other Flash Pro projects. The process of creating a library in Flash Pro wasn't obvious to me, so I thought it might be useful to show how it was done. Thanks to Trevor McCauley for helping me figure this out. Trevor's on the Flash player team and is very helpful is figuring out all kinds of things about how Flash works, including this bit about Flash authoring. Check out his blog at senocular.com.

Here's how this relates to Flex and my work for Flex: the library I create is an animation library based on the core facilities underlying the Flex effects. I call the library Flexy.

Flex effects are pretty dependent on the Flex library overall. They use concepts in UIComponent, graphic elements, styles, and other concepts that are found in the core Flex framework. So building a full-on Flex effects library in Flash Pro would necessarily drag in so much of the Flex infrastructure that it wouldn't be worth the effort; you might as well just use Flex directly.

But the underlying animation engine that the new Flex 4 effects depend on, including the Animation class, arbitrary type interpolation, and the new easer interface and classes, are independent of Flex effects and of nearly everything else in Flex. I wanted to see if I could break those animation facilities out into a separable library. There were various niggling dependencies, such as the use of the EffectEvent class, some use of resource bundles for error strings, and other minor issues, but one 50-minute train commute later, I had a 13k library built in Flash Pro that had no Flex dependencies and had all of the underlying animation support that the Flex effects use for running their animations.

Flexy doesn't handle all of the effects functionality, of course, because Flex effects aren't part of the port. Flex effects provide mainly two extra layers of support for animations. First, they handle setting animated values on target objects/properties. The animation infrastructure only handles calculating the animated values, not what to do with those values. Second, effects have extra built-in logic and convenience APIs that makes it easier to declare and run effects than you might otherwise have to do with a more manual approach to animation.

I can't do much about the second bit; that's most of the code in effects, and I was looking for something simple. But for the first part, I've provided a utility class called PropertyAnimator which you can use to automate assigning animated values to properties, just like effects do. It simplifies using Animation and makes very easy property animation possible with this library.

Check out Flexy and see what you can do with it. And come back for the next show to see a simple example of using the library from within Flash Pro.

Here's the video:

There is no demo application this time, because the demo is really just the reusable Flexy library, which isn't very exciting to watch, as demos go, because it just sits there, being reusable.

Here is the demo code. This is a zip file containing the Flash Pro project (Flexy.fla), the Flexy library itself (Flexy.swc), and the source code that Flexy was built with.

Users of the Flex Animation class should be able to start playing with the library pretty easily, but if the API is a bit of a mystery to you, check back for my next show, in which I'll dmeonstrate how to use the library in Flash Pro for a simple example.

Here's where you can find CodeDependent on iTunes.

And here's where you can find CodeDependent on YouTube.

Enjoy.

Thursday, December 3, 2009

Video: Measuring Frame Rate Performance

Measuring Frame Rate Performance, the next episode in the gripping and suspenseful series CodeDependent, is now available from Adobe TV.

This show different notions of "frame rates" and performance in Flex applications, how they relate to each other, and ways of measuring them. Frame rates directly affect the perceived performance and smoothness of animations, so it's something near and dear to my geeky animation heart. Measuring performance may not be something you need to do in every (or any) application, but it's good to know what's going on under the hood and how to get more information if you do need it.

I neglected to mention an important detail about 'render' events (thanks, Corey). In Flash applications, if you call updateAfterEvent() to cause a render event (as we do internally in Flex effects when handling Timer events), you actually need to call stage.invalidate()to make sure that the player sends out render notifications. Without the invalidate() call, the player will render, but will not dispatch any render events. In the case of our example application here, we don't have to invalidate() anything to get the render events because the Flex layout system is calling invalidate() for us.

Here's the video:

Here is the demo application:

And here is the source code.

Finally, here's where you can find the CodeDependent videos on iTunes.

Enjoy.

Tuesday, September 22, 2009

Video: 3D Effects in Flex 4

3D in Flash Player 10 and Flex 4, the next episode in the gripping and suspenseful series CodeDependent, is now available from Adobe TV.

In this show, we see some of the 3D capabilities in Flash Player 10 and how we take advantage of them for some of the new 3D effects in Flex 4.

Here's the video:

Here is the demo application:

And here is the source code.

Finally, here's where you can find the CodeDependent videos on iTunes.

Enjoy.

Friday, May 22, 2009

Video: CodeDependent #2, er, 7

Now things are really moving...

The next episode of CodeDependent, "A Moving Comparison," is out. There are actually 7 episodes in the show so far, but 5 of those are reruns, rendered onto the spiffy new CodeDependent set. (I suspect that the earlier versions of those episodes, rendered on the standard Aodbe Developer Connection backdrop, will become collectors' items. Somewhere. Sometime. Perhaps they will be re-master and re-released someday, as "Codedependent Classics").

This latest show is a brand new one, starting right where the first one, "Graphics in Flex 3 and Gumbo", left off. In the first episode, I compared simple custom graphics in Flex 3 to simple graphics in Flex 4 (codename Gumbo). In this episode, I compare simple animations in Flex 3 and Gumbo on those same graphics objects.

Here's the video:

Here are the demos:

And here is the source code.

Enjoy.

Monday, May 11, 2009

Video: CodeDependent #1

Adobe TV has just posted the first in what I hope to be a very long run of my new show, “CodeDependent”. Fortunately, the show is not on the Fox network, so that may give it a better chance of not being canceled.

The CodeDependent show is not that different from the videos I’ve been doing already: short tutorials on various aspects of Flex and Flash that I find interesting. The biggest change is that they should be more regular (we’re aiming for an every-other-week schedule as we settle into it). Also, they producers gave me a nifty new grayscale CG set design to match my nifty old grayscale CG personality.

This first show is a simple tutorial on Flex 3 and Flex 4 (codename Gumbo) graphics. For the Flex 3 demo, you'll see how to drop down into ActionScript code to draw simple graphics through the Flash APIs into your Flex application. For the Flex 4 version, you'll see you to do the same thing through MXML graphics tags. In future episodes, we'll build on these simple examples and see how to animate the objectsand how to get more realistic animations. (Can you feel the suspense of this cliff-hanger? I’m trying to incorporate standard television series techniques into the show. Look for a laughtrack soon.) But I’m getting way ahead of myself.

Here’s the video:

Here’s the awesome demo application:

And here’s the source code.

Enjoy. And welcome to my show…

Thursday, January 29, 2009

Video: MAX Presentation on Filthy Rich Flex Clients

Adobe TV has just posted my session Filthy Rich Flex Clients, which was recorded at MAX 2008. It's a screencast with audio, so you get the dual benefits of seeing the slides/demos and not having to watch me deliver them.

It's a pretty dense presentation, in which I cover some Flash platform graphics fundamentals, how effects work in Flex3, how they will work in Gumbo, and (now that I built up the rest of that material) how to do some particular 'Filthy Rich Client' effects in Flex.

Check it out at Adobe TV in larger resolution, or watch it from the comfort of this handy blog post:

In case the text in the video is hard to read, here's the presentation in PDF format for your amusement.

Wednesday, October 22, 2008

Video: Glow For It

Here's another Adobe TV video in the Flex in a Week training series. This one's about using Flash filters in Flex applications. Specifically, I discuss instantiating and animating a Glow filter on a button, but the techniques discussed are applicable to using filters in general. The source code is fairly self-explanatory, but feel free to grab the two files I cover in the video: GlowingButton.mxml and AnimatedGlowingButton.mxml. Glow ahead, check it out!

Wednesday, October 15, 2008

Video Time

Here's another video to watch, on some of the details about using the Flash Timer. I shot this video as a part of a just-posted series for the Flex in a Week training shows. This is the same topic and application that I cover in my earlier Time's Up blog entry, but in video form. It's so much more dramatic this way. And video seems so much more fitting for the material. I'm talking about time, after all, so why not cover the topic using time-based media? It was either this or a song, and I thought I'd save you that torture.

Tuesday, September 9, 2008

Flexy: A Flexible AS3 Animation Library

I'm currently working on new animation capabilities for the next release of Flex. We have some great stuff in there already, such as the effects infrastructure and state transitions, but I'm rounding out the functionality and rethinking some of the APIs to make the system more powerful and usable by both tools and developers.

For my effects work, I'm mostly concerned about Flex developers because, well, that's what the library is for.

But it occurred to me, through conversations and through random web perusals, that an animation system that could also be used outside of Flex might be interesting to the people doing ActionScript3 applications without the Flex framework, or to people doing both Flex and non-Flex AS3 programming. It also occurred to me that much of my code was Flex-agnostic. Of course, the code that is tied to Flex effects and transitions is necessarily dependent upon Flex, but the core animation engine really doesn't care what it's animating or how; it's simply varying values over time.

So I took a pass over the code to make it truly Flex-less, which involved removing a total of like 5 lines of code from my existing classes. I also added a class called ObjectAnimator, which is a simplified version of the new Flex Animate effect class.

But probably more interesting to the people reading this, I am putting out the results here as something for people to try out.

Caveat: (I love using the word Caveat. For one thing, it's a great hedge. But also, it's the only Latin word I know and surveys show that people who speak Latin are smarter. Except the Romans, when they let their city get overrun and their empire trampled) This is not a released product, either from Adobe or from me. Instead, it's a technology demo that shows you what the new Flex animation system does. My main focus is, and should continue to be, making Flex animation rock, which means that I can't spin up effort supporting an animation library that's a separate entity. Having said that, the code that I'm delivering here is about a 99% (taking a complete guess because I don't want to bother diffing and counting lines of code) copy of the code in Flex, so it's not like this is just some random code project spew.

There are various reasons I'm doing this:

  • Nice guy: This is just another example of what a great guy I am. A real pal. Your source for fun code and good CPU times.
  • Feedback: I'd like to get people, especially animation-savvy developers, interested in what I'm doing for Flex and giving me some feedback on things they think I'm missing, or things they would also like to see, or problems that they are having. I don't want Flex to be a constraint that makes it hard for those people to check out the animation code and play with it.
  • Share the wealth: If the stuff I'm doing for Flex can be useful in other Flash contexts, great! As I said, my main focus needs to be Flex. But if I'm doing generic work that can be used elsewhere in the Flash world, that's okay by me.

Demos are usually good, so here's one:

And here's the source code. The demo was built in Flash authoring (although you should be able to get the same result in either Flex or pure AS3 programming - I just used Flash as a proof-of-concept for this non-Flex animation library). The creation of the ball and button aren't obvious from the code because I did it in the authoring tool, but they aren't the main course of that demo anyway, and it would be easy to write something similar purely in code.

I was about to post this article, but then I realized that maybe you'd be interested in what the animation library actually does. Here are some quick explanations, but feel free to check out the demo code and the ASDocs for more concrete details:

  • Property Animation: Animating one or more properties on a given object for a set duration. This means calculating the in-between values and setting them on the object directly.
  • Arbitrary Type Interpolation: Animation of properties of any type: as long as you supply an IInterpolator implementation to the animation it can figure out how to calculate the in-between values. For example, there is a ColorInterpolator supplied with the library that handles per-channel interpolation for RGB uint values.
  • Easing: There is a simple IEaser interface with several built-in implementations to enable various linear and non-linear time easing calculations, providing for more realistic animations. For example, the Bounce demo above uses a cubic Power ease that accelerates for the full duration down.
  • Repetition. I repeat: Repetition: Animations that automatically repeat, either looping or reversing, for specified numbers of times. For example, the Bounce demo specifies a repeatCount of 2 that reverses each time to get that in-out behavior of both the bounce-down/up and the squish out-in effect.
  • Size Matters: The library is 10k. 10k! That's like less memory than Bill Gates thought anyone would ever want on the PC! Less than the word count of this blog (if I'd repeated each word 10 times)!
  • Multiple Leves of Control: ObjectAnimator exists to make creating animations even easier; you don't have to handle the animation events, but rather just tell it what properties to animation on which object between which values, and ObjectAnimator does the rest. But if you'd like to get your hands dirty, it's not that much more work to create the Animation object directly. Here's source code for an ObjectAnimator-less version of the same demo. Note that the steps to creating the animation are just about the the same as in the other version, but that we also have to set up and handle the update events in order to actually move the target object.

In the future, I may try to integrate this side-project more closely with Flex. For example, it's possible to build parts of the Flex framework (or any Flex project) as a separate library. For now, just to expedite getting this out there, I copied the code and created this project in Flash (that also helped me ensure that there were no Flex dependencies). But stay tuned to see if/when I start using the same source/build base for this project as for the rest of my Flex work.

So take the library. Try it out, with Flex projects and non-Flex projects. Check out the ASDocs. Check out the demo code. Check out the Flex opensource site for the actual code (only ObjectAnimator does not exist in Flex and most of that is culled from the Animate/AnimateInstance classes). Let me know how it works for you and what else you'd like to see in a core animation package.

Oh, and tell your friends what a nice guy I am.

Friday, September 5, 2008

Presentation: Filthy Rich [Flex] Clients

The video for my recent presentation at the 360Flex conference was just posted. Here it is for your viewing pleasure. Grab some popcorn, turn down the lights, and settle back in your chair for this action-packed Flex thriller:

Tuesday, June 10, 2008

Video: Pulse

Adobe TV surprised me today by posting my Pulse demo much sooner than I thought they would. Four videos posted in just over a week. I feel like Ben Affleck, where everyone will probably get sick of me when I'm just getting started. Except that Ben has a way bigger jaw and whiter teeth. And I bet he can't program graphics applications. This demo is the first in a series of "Filthy Rich Client" applications, where I look at what it would take to enable specific graphical or animated effects on the Flex platform. The Pulse demo in particular is a Flex take on an application originally written in Java by Romain Guy. The only problem is that I wasn't quite ready to post the source (just have to go through it and make sure it's ready for leaving the nest: nicely commented, teeth brushed, lunch packed...). So watch the video for now and check back here soon if you're interested in seeing the source code and application. By the way, this kind of content is also being posted on the Flex Developer Center, so you might also put that site on your list of Flex learning bookmarks.

Friday, June 6, 2008

Video: Top Drawer (Parts II & III)

Adobe TV has posted the second and third installments of the Top Drawer videos that we made, showing how we turn mouse events (Part II) into graphical shapes (Part III). Be sure to also check out my original Top Drawer postings (here, here, here, and here) that link to the source code for the project.

Monday, April 14, 2008

Time's Up

In trying to figure out the wacky results I was seeing from running the BubbleMark application, I discovered that timer events are constrained in Flash (and therefore Flex) when running inside the browser. This limitation extends to all methods of timing, including: the Timer class, handling ENTER_FRAME events, and manually calling callLater().

Meanwhile, using these timing methods outside of the browser, either in the standalone Flash player or in an AIR application, returns very different results because Flash is no longer constrained by the browser's throttling.

I wrote a simple application, TimerBenchmark, to show the differences between the different timing mechanisms inside the browser and out. The user selects the timing mechanism desired, optionally enters a resolution value (the milliseconds between timing events) for the Timer case, and the test automatically runs. As the test runs, it counts how many timing events are executed per second and displays that result in the "FPS" field at the top of the application. For example, here's the application, runnin in your browser:

How it Works

First, let's see the code. Everything is in the TimerBenchmark MXML file, which you can download and play with. The GUI is set up with a combination of labels, radio buttons, and a TextInput control for the Timer resolution:

    <mx:Label id="fps" x="75" y="48"/>
    <mx:Label x="10" y="48" text="FPS"/>
    <mx:Label x="114" y="76" text="Timer resolution"/>
    <mx:TextInput x="218" y="74" width="39" id="resolution"
        enabled="{timerButton.selected}" text="1" enter="restart()"/>
    <mx:RadioButtonGroup id="benchmarkType" change="restart()"/>
    <mx:RadioButton id="timerButton" x="10" y="74" label="Timer" groupName="benchmarkType"/>
    <mx:RadioButton id="enterFrameButton" x="10" y="100" label="enterFrame" groupName="benchmarkType"/>
    <mx:RadioButton id="callLaterButton" x="10" y="126" label="callLater" groupName="benchmarkType"/>

Any change in the Timer resolution input or one of the radio buttons will call restart(), which will reset any running benchmark and then execute the appropriate test:

            private function restart():void
            {
                if (timer)
                    timer.stop();
                removeEventListener(Event.ENTER_FRAME, enterFrame);
                callLaterRunning = false;
                runBenchmark();
            }
            public function runBenchmark():void
            {
                switch (benchmarkType.selection) {
                    case timerButton:
                       startTimerTest();
                       break;
                    case enterFrameButton:
                       startEnterFrameTest();
                       break;
                    case callLaterButton:
                       startCallLaterTest();
                       break;
                }
            }

All of the tests call benchmarkCallback() to do the actual work of timing the mechanisms:

            private function benchmarkCallback():void
            {
                ++numFrames;
                var nowTime:int = getTimer();
                var delta:int = (nowTime - startTime);
                if (delta > 1000)
                {
                    fps.text = String(numFrames * (delta / 1000));
                    numFrames = 0;
                    startTime = nowTime;
                }
                if (callLaterRunning)
                    callLater(benchmarkCallback);
            }

benchmarkCallback() counts the number of times that it's been called since the last measurement time. Every second, it calculates the fps result, based on numFrames and the time elapsed since the 0th frame. It updates the fps label appropriately and then resets numFrames and startTime. Note that our benchmark function doesn't actually do anything useful. It's a bit like our politician, except that runBenchmark() is intentially unproductive because we only want to time how fast the timing mechanisms can run, not how fast we process anything when they get there. It's important to be able to disentangle these concepts from each other, lest we run into the confusion I encountered in the BubbleMark program, where the throttled rate of Timer was affecting the perceived computation or rendering performance of the application.

startTimerTest() is the function called when the Timer button is pressed:

            public function startTimerTest():void
            {
                if (timer && timer.running)
                    timer.stop();
                timer = new Timer(Number(resolution.text));
                timer.addEventListener(TimerEvent.TIMER, timerEvent);
                timer.start();
            }
            private function timerEvent(event:TimerEvent):void
            {
                benchmarkCallback();
            }

This mechanism works by asking the Flash runtime to call our callback function, timerEvent(), every n milliseconds, where we set n to be equal to the value in our "Timer resolution" text input control.

The enterFrame mechanism looks like this:

            private function startEnterFrameTest():void {
                addEventListener(Event.ENTER_FRAME, enterFrame);
            }
            public function enterFrame(event:Event):void
            {
                benchmarkCallback();
            }

This mechanism works by telling the Flash player to call our callback function, enterFrame(), whenever the ENTER_FRAME event is processed (which is, by definition, once per 'frame' that Flash renders).

Finally, the callLater() benchmark is run by the following code:

            private function startCallLaterTest():void
            {
                callLaterRunning = true;
                benchmarkCallback();
            }

in addition to the final two lines of our benchmarkCallback() function:

                if (callLaterRunning)
                    callLater(benchmarkCallback);

This mechanism works by asking the Flash runtime to call our function, benchmarkCallback(), at the next available opportunity, which occurs twice per frame.

Resolution Results

Here are some sample results with the application running inside the browser on my Windows Vista system:

As you can see, the Timer and EnterFrame mechanisms are both throttled at about 65 frames per second. Meanwhile, the callLater mechanism is getting serviced at about twice that rate.

The reason for the throttling is that the timing system that Flash uses inside the browser is pegged at a maximum, which is a combination of a limitation of the timing mechanism of the browser itself and the refresh rate of the monitor (which for my laptop is 60 fps). The main reason for this limitation is to avoid pegging the CPU just to run animations that really don't need to run faster than the monitor's refresh rate. After all, why waste time and CPU resources updating something that the user will only see updated at the refresh rate of the monitor?

Both the Timer mechanism and enterFrame are maxed out to this same limit. The callLater mechanism sees about twice this rate because it is actually called exactly twice per frame in Flash. It is called once when leaving the enterFrame() call and another when processing the actual frame rendering, so an application that requests a callback through callLater can expect to get that callback twice per frame.

Meanwhile, here are some results from running in the standalone Flash player:

Now we can see that the Timer approach gets close to our ideal fps number; since the resolution was set at 1 and we set the internal Flash frameRate property to 1000, the ideal result is 1000 frames per second. The enterFrame mechanism seems to be throttled at a much lower 250 fps, although this is still significantly better than the browser limit of 65 fps. The callLater mechanism is again twice that of enterFrame, which is what we would expect.

I wrote an AIR version of the benchmark as well. I won't bother to post that version, since I got the same results as the standalone Flash player. This is as expected, since the AIR version is basically running the Flash player standalone internally, freed from the browser constraints. So it is essentially equivalent to the standalone Flash player version.

Resolution Resolution

So where does that leave things? What are we to make of the different timing results?

Well, we mainly need to be aware of the issues, especially when running timing-sensitive code. What mechanism should you use? What frame rate do you want? Are you really getting the results you think you are, or is there some environmental effect that is having an impact on the timing of your application?

In most real-world situations, your application need not (and probably should not) run at a faster rate than the resolution of the screen, so the limitation of the timing mechanisms to refresh rates is perfectly reasonable. In particular, in graphical applications any updates to the scene at greater than refresh rates is simply a waste of cycles, since the user won't see that many updates. But if you need to run some function at greater than this rate for some reason, be aware of the factors called out above and maybe even do some benchmarking of your own to make sure you're getting what you think you're getting.

For More Information

Here's my previous post on Timer's effect on the BubbleMark results

Here's James Ward's post on the same topic, with some alternate benchmark applications and results

Here'sa blog post by Adobe's Tinic Uro on the throttled frame rate in Flash

Here's the original BubbleMark site

Here's the source code for my application, TimerBenchMark

Here's the Flash SWF file for the benchmark

Wednesday, April 9, 2008

Off the [Bubble]Mark

James Ward has been looking into the BubbleMark application recently, and we were talking about some of the strange results we saw in different situations. For example, I was able to take the existing application, which gets something like 65 frames per second in IE7 on my Vista laptop, recast it as an AIR application, and get about double that frame rate, or 130 fps. James posted some variations of the application on his blog; check them out to see what I mean.

(Some background, for those that didn't avail themselves of the awesome opportunity of clicking on the recent link to the site: BubbleMark is an application that moves a number of 'balls' around in a window, and bounces them off one another while doing so, and then measures how fast this critical and awe-inspiring process happens per second).

What's going on? Is that gremlin in my keyboard back to haunt me? Or have I been staring at the screen too long and I'm mis-reading the numbers? Or maybe age is creeping up on me and I'm just getting things wrong more often, as a prelude to slipping on the ice and breaking my hip.

Circular Reasoning

The fundamental problem with the current test is that it is limited by the resolution (the time between events) of the timing mechanism used by the application. In this case, the timing mechanism is a Flash Timer object that is scheduled to call our code at frequent intervals (supposedly far more frequent than the frame rate we're seeing). So while the application purports to test the number of frames that it can render per second, it actually tests the number of times per second that the Timer sends out events. In the situation where Flex can render the balls much faster than the Timer frame rate, this means that the frames-per-second (fps) reported has very little to do with the speed at which the balls are being moved or rendered.

By producing an alternate test that runs on AIR (which takes Flex out of the browser and runs Flex applications in a standalone window on the desktop), we remove a constraint where the Timer resolution tends to be more limited in the browser than in standalone applications. Since the Timer can call our ball-moving routine more often in this AIR version, and since we were taking less than a frame's worth of time to move the balls before (although that wasn't reflected in the fps result), we get a higher effective frame rate and better results.

So the AIR version is closer to the ideal, where we're actually measuring more of what the test was presumably intended for; moving and rendering the balls.

But there's still a problem here, which has been implied by the wide variation in the results. The test is confusing three orthogonal issues and trying to combine them into one result (fps). The three things actually being tested are:

  • Calculation speed: How fast can the runtime position the balls, including moving them, calculating collisions, and offsetting the collided balls? This comes down to VM and math performance, since it's just raw computations.
  • Rendering performance: There is a certain amount of time spent each frame actually drawing the balls on the screen. That seems to be insignificant in our case, compared to the other factors here, but you would think that a graphics benchmark might actually care about this item them most. How fast can we draw the balls? I don't know, because we're spending most of our time just waiting around for Timer events and calculating the balls' positions. That's like timing how long it takes you to get dressed in the morning, but starting the timer the previous night when you go to sleep.
  • Timer resolution: This is the primary issue called about above. I don't believe it was ever the intention for the benchmark to measure the rate at which the various runtimes could schedule callbacks. Because, well, that'd be a pretty dull graphics benchmark. But that's exactly what's happening here; we're not measuring graphics performance at all in the default test.

To add to the problem of these three factors getting each others' chocolate stuck in the others' peanut butter, you've got the problem that all of the different platforms (Flex, Java, .net, etc.) probably have different reliance on these three factors (are they faster at rendering but slower than computation? Do they have more limited Timer resolution? How do browsers affect these factors on each platform? Or hardware? Or operating systems?)

I think that if you wanted real results that you could use and compare from this benchmark, you'd have to de-incestuate the factors and measure them all separately. I could envision three competely separate tests coming out of this:

  • Calculation speed: Let's have a non-graphical test that simply moves/collides the balls as fast as possible and reports how many times it was able to do this per second. No need to render here; we only care about how fast we can do the math and related setting of properties.
  • Rendering performance: Render a given number of balls as fast as you can and report how often you can do it per second. In order to not run up against the Timer resolution issue, you'd have to adjust the number of balls so that you could be assured that you would be spending very little time waiting around between callback events. In fact, you could even try to render far more than could be done in a single frame, but then report a number like balls/second instead of frames/second.
  • Timer resolution: Write a test that simply issues callbacks from the Timer mechanism, without doing any work in those callbacks, to see how many times this happened per second. (I wrote this test and posted a blog about it; see that post for more information on this no-op timer test).

With results from these three tests, it would be easier to understand where a platform was being bottlenecked: at the runtime, rendering, or resolution level.

At this point, I'd love to say, "...and here's that benchmark!" But I'm not going to, because I've already spent a pile of time just trying to understand the weird results I was seeing from Bubblemark. If someone else wants to take a whack at it, that'd be great. But at the very least, I hope this helps you understand and filter the results you see from the BubbleMark tests a little better.....

Tuesday, March 25, 2008

Lines and Tigers: Customizing ComboBox

In the Top Drawer application that I posted recently, wanted a custom combobox that showed the line widths as graphical lines, not as text descriptions. Text is a pale substitute for graphics, don't you think? Think how much more boring Hamlet would have been if it had just been a bunch of words. Or Anna Karenina. Or think how exciting and immersive everey episode of Sponge Bob Square Pants is, all because it's drawn in such meticulous detail.

I got part-way to my goal in Top Drawer, with the drop-down list showing lines instead of labels. But the main combobox button still showed a text label, instead, "width 1":

Partial Achievement

Now that I finally finished the exposee on Top Drawer, it's time to revisit the combobox and see if we can spruce it up a bit.

Take I: Default ComboBox with Boring Text Labels

First, let's see the default we'd get if we just coded up a normal ComboBox to handle line widths. In this case, I want to assign a width between 1 and 5 to my lines; I create a ComboBox with a list of objects that have width attributes.

    <mx:ComboBox id="lineWidth" x="10" y="189" width="87"
            editable="false">
        <mx:ArrayCollection>
            <mx:Object width="1" label="width 1"/>
            <mx:Object width="2" label="width 2"/>
            <mx:Object width="3" label="width 3"/>
            <mx:Object width="4" label="width 4"/>
            <mx:Object width="5" label="width 5"/>
        </mx:ArrayCollection>
    </mx:ComboBox>

The width attribute is used as a source in a databinding expression for the ArtCanvas object, where it sets the current stroke width based on the width value of the currently selected item in the ComboBox.

    <comps:ArtCanvas id="canvas" 
            drawingColor="{drawingColorChooser.selectedColor}"
        strokeWidth="{lineWidth.selectedItem.width}"
        left="105" top="10" right="10" bottom="10"/>

This ComboBox does the job, allowing the user to set the width of the lines interactively through the GUI with the small amount of code above to hook it up. But the user experience is not quite what a graphics geek might want:

Default Line Width ComboBox

Take II: Graphics in the Drop-Down

Now we come to the version that I developed in the Top Drawer application; I attacked the drop-down list to make the elements in that list graphical instead of the boring text-based "width x" versions above.

To do this entails the Flex notion of an "item renderer." Item renderers are used by the list-based components, such as ComboBox, allowing applications to specify objects which will perform custom rendering of each item in the list. Specifying the item renderer in MXML is done by simply naming the class that will be used to render the items. In our application, we use the class LineWidthRenderer, which is specified in the same mx:ComboBox tag as above:

    <mx:ComboBox id="lineWidth" x="10" y="189" width="87"
            itemRenderer="components.LineWidthRenderer" editable="false">
        <mx:ArrayCollection>
            <mx:Object width="1" label="width 1"/>
            <mx:Object width="2" label="width 2"/>
            <mx:Object width="3" label="width 3"/>
            <mx:Object width="4" label="width 4"/>
            <mx:Object width="5" label="width 5"/>
        </mx:ArrayCollection>
    </mx:ComboBox>

The code in our LineWidthRenderer class (written in ActionScript3) that handles rendering each item is in the standard setter for the data field; this method will be called by Flex whenever it needs to display a particular item. In our case, we know that each data item will contain a width attribute, so we check the value of that attribute and perform our rendering appropriately:

        override public function set data(value:Object):void
        {
            _data = value;
            renderLine(graphics, 5, 50, _data.width);
        }

        public static function renderLine(graphics:Graphics, 
                x0:int, x1:int, lineWidth:int)
        {
            graphics.clear();
            graphics.lineStyle(lineWidth, 0x000000);
            graphics.moveTo(x0, 10);
            graphics.lineTo(x1, 10);
        }

Here, our data setter is calling the renderLine() method in the same class, which takes a Graphics object, endpoints for the line, and a line width value and draws an appropriate line into the display list of the graphics object. The result is much better than before:

Better Line Width Rendering

Finally, we're rendering our line width choices as actual lines. The user can see visual representations of what they will get in the shapes they draw and can make a better selection between the UI choices. Besides, it just looks cooler.

But it's still not quite good enough; what's with that "width 1" label in the ComboBox's button?

Take III: ComboBox Nerdvana

Not only does the button of the ComboBox display text when the drop-down list is nicely graphical below it; the closed ComboBox displays only that text label. Gone is all of the lovely wide-line artwork that we slaved over for the drop-down list.

The final step, then, is to customize our ComboBox further to provide a graphical representation of the currently selected item in the top-level button.

This turns out to be a simple problem to solve. So simple, in fact, that I felt silly that I didn't solve it before I shipped the initial version of the application. But what would life be like if all our TODOs were impossible? Here's the solution:; we need a subclass of ComboBox with customized rendering for the button.

We create a subclass of ComboBox in ActionScript3, LineWidthComboBox, as follows:

package components
{
import mx.controls.ComboBox;

public class LineWidthComboBox extends ComboBox
{
    override protected function updateDisplayList(
            unscaledWidth:Number, unscaledHeight:Number):void
    {
        LineWidthRenderer.renderLine(graphics, 10, 55, selectedItem.width);
    }
}
}

Not very complex, is it? The subclass exists to override the single method updateDisplayList(), which is where a component creates the rendering for its contents. In this case, we call the same static renderLine() method as we used before in our LineWidthRenderer class to draw a graphical representation of the currently-selected line width into our component. But apparently this isn't enough, since this is what results:

Where'd the Buttoin Go?

The problem is that we're not bothering to render the actual button object of our ComboBox, so the typical button background that we expect is not there. Since we're lazy and don't want to do all of that work in our code, we can ask the superclass to take care of the rendering for this component for us by simply calling the superclass' updateDisplayList() method first, and then performing our custom rendering on top of it:

    override protected function updateDisplayList(
                 unscaledWidth:Number, unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        LineWidthRenderer.renderLine(graphics, 10, 55, selectedItem.width);
    }

This is better, but still not quite what we want:

Label and Line

Now we have our button and a graphical line, but we also have the default text label which our superclass is kindly drawing for us. We want the superclass to draw the button decoration, but we don't want it to draw the text label. How can we fix this?

There are probably several ways to do this, but one easy way is to simply supply a label that won't get drawn.

By default, ComboBox looks for a "label" attribute in each item in the list and renders the string associated with that attribute. In our original mxml code for the ComboBox, we supplied items with both a width property (which supplies the information we use to actually determine the stroke width) and a label property, as follows:

        <mx:ArrayCollection>
            <mx:Object width="1" label="width 1"/>
            <mx:Object width="2" label="width 2"/>
            <mx:Object width="3" label="width 3"/>
            <mx:Object width="4" label="width 4"/>
            <mx:Object width="5" label="width 5"/>
        </mx:ArrayCollection>

We could simply remove the label:

        <mx:ArrayCollection>
            <mx:Object width="1"/>
            <mx:Object width="2"/>
            <mx:Object width="3"/>
            <mx:Object width="4"/>
            <mx:Object width="5"/>
        </mx:ArrayCollection>

but then we wouldget the following instead:

Bad Label and Line

The problem this time is that ComboBox is determined to find a text label, or if it doesn't find one it will create one of its own, which is obviously not what we want.

What our button really needs is an empty label; so let's provide it:

        <mx:ArrayCollection>
            <mx:Object width="1" label=""/>
            <mx:Object width="2" label=""/>
            <mx:Object width="3" label=""/>
            <mx:Object width="4" label=""/>
            <mx:Object width="5" label=""/>
        </mx:ArrayCollection>

With this change, we've pointed the ComboBox to label properties that it can use, but when our superclass goes to display the text, nothing will happen - which is just what we want:

Just the Line

Finally, with our custom ComboBox subclass, our reliance on the superclass rendering facilities for the button basics, and our fabulous new empty labels, in addition to our previous use of a custom item renderer for the drop-down list, we have what we set out to achieve: a completely graphical line-width control:

Completely Graphical Line Width Control

Here are the update sources Top Drawer. Stay tuned for more improvements in upcoming posts.

Wednesday, March 19, 2008

Top Drawer, Part III: Taking Shape

Today, we finish our walkthrough of the Top Drawer application.

But first:

Q: Why did the Shape leave the canvas?
A: It had had its fill.

Q: What do they call boxing matches between vector-art objects?
A: Stick-fighting.

Q: Why don't Shape parents allow their kids to watch stick fighting?
A: Too much graphic violence.

Q: How are Shapes kept informed?
A: First, they're given an outline, then they're filled in completely.

Q: Why was the drawing application so tired?
A: For every four lines drawn, it was a complete rect.

Q: What do these jokes have in common with inidividual vectors drawn wth TopDrawer?
A: They're both one-liners with little point.

In Part I, we went over the code for TopDrawer.mxml, which contained the GUI layer for our drawing application. In Part II, we saw the code for ArtCanvas.as, which handles the interaction and most of the logic of the application. Now, in Part III, we will see the code for creating and rendering the actual drawing shapes.

More importantly, in this installment we'll actually finish the series (at least until a later post where I'll go over some design improvements).

To refresh your memory, here's the amazing drawing application again:

Helper Classes

Before I get to the [he]art of this article (the shape classes), I wanted to cover a couple of other small classes to show how they do their thing.

LineWidthRenderer.as

This class is repsonsible for rendering each item in the drop-down menu on the line-width combobox in the main GUI. Instead of the typical text-based combobox, I wanted to have a graphical representation of the lines, like what you see in most drawing applications. An "item renderer" is a class that can be installed on many of the Flex list-based components to render each individual item in those components. It does this by receiving calls to set the data member of the item and producing an appropriate rendering. In our data-setting method, we simply take the information embedded in that data item (which includes the width field, set in TopDrawer.mxml) and create a display list for our LineWidthRenderer class which will draw a line of that width:

        override public function set data(value:Object):void
        {
            _data = value;
            var lineWidth:int = _data.width;
            graphics.clear();
            graphics.lineStyle(lineWidth, 0x000000);
            graphics.moveTo(5, 10);
            graphics.lineTo(45, 10);
        }

[To make this even better, I'd like to enhance the combobox so that the button that's displayed also has a graphical line and not the text label it has now. Look for that improvement in a future installment.]

DrawingShapeIcon.as

DrawingShapeIcon is a simple subclass of Image which has the added functionality of displaying a red highlight border when it is the currently selected drawing mode. We saw in Part I how the DrawingShapeIcon objects get created in TopDrawer.mxml added as event listeners when the drawing mode changes. Now we'll see how the highlight border is implemented.

The drawing mode assigned to each DrawingShapeIcon is set by assigning the mode variable:

        public var mode:int;

The border is a static object (we only need one for the application since all icons can reuse the same one):

        private static var highlightBorder:Shape;

Finally, the modeChanged() method is called when the DrawingModeEvent is propogated. This method creates highlightBorder if it does not yet exist and then adds the border to the instance whose mode matches the new mode that's been set. Note that adding highlightBorder as a child on to one icon will automatically remove it as a child from any other icon; we do not need to bother calling removeChild() for the previously-highlighted icon.

        public function modeChanged(event:DrawingModeEvent):void
        {
            if (!highlightBorder) {
                highlightBorder = new Shape();
                highlightBorder.graphics.lineStyle(5, 0xff0000, .8);
                highlightBorder.graphics.drawRect(0, 0, width, height);
            }
            if (event.mode == mode) {
                addChild(highlightBorder);
            }
        }

ArtShape.as

Now, we're into the meat of this article: the drawing shapes. All of the shapes are subclasses of ArtShape, which provides the public API for shape creation and performs some common functionality that is used by most subclasses. ArtShape is a subclass of the flash class Shape, and ArtCanvas uses capabilities of that class, such as being able to add it as a child of the canvas and being able to detect whether a given point on the canvas hits the object (we saw both of these capabilities exercised in Part II, when we explored ArtCanvas).

Properties

Each shape keeps track of its start and end points, which comprise the points where the mouse first started dragging and the last drag location of the mouse. These are currently only used during object creation, when endPoint may change with every mouse drag. Once a drawing is complete, the rendering of a shape is not changed so we do not actually need these points again. A more complete drawing program might allow these points to be edited after the fact, in which case caching them would be useful.

        protected var startPoint:Point;
        protected var endPoint:Point;

The strokeWidth and color properties are set at construction time and cached for later use when creating the display list for the shape. Note that strokeWidth is only used in the non-filled primitives (a more exact class hierarchy might differ slightly to avoid caching an instance variable at this level that is not used by some subclasses):

        protected var strokeWidth:int;
        protected var color:uint;

The start() and drag() methods are called upon first mouse-down and each drag operation, respectively. The start() method merely caches the current start and end points, whereas the drag() operation, in addition to setting a new end value, also calls renderShape(), which is where all subclasses (except Scribble, which we will see later) create the display list which renders the shape:

        public function start(value:Point):void
        {
            startPoint = value;
            endPoint = value;
        }
 
        public function drag(value:Point):void
        {
            endPoint = value;
            renderShape();
        }

The addPoint() method is called on mouse-up. Once again, the new endPoint is cached and renderShape() is called. We also return a value that indicates whether this shape is complete. The reason for this is that a possible future feature would allow drawing of complex primitives such as curves, which might take several mouse clicks/drags to create a single shape, instead of the single drag operation that the current shapes take. This return value would tell ArtCanvas whether to record the shape as finished or to continue in a mode of adding information to the current shape. It's not a part of the current application, but I added this little bit of infrastructure in case I added it later.

        public function addPoint(value:Point):Boolean
        {
            endPoint = value;
            renderShape();
            return true;
        }

The validShape() method is called by ArtCanvas after the shape is complete to determine whether this shape should actually be added to the list of shapes on the canvas or whether it should be deleted. The simple check in ArtShape simply checks whether the start and end points are the same. Subclasses may choose to have more complex check (which is the case in the Scribble shape).

        public function validShape():Boolean
        {
            if (endPoint.equals(startPoint))
            {
                return false;
            }
            return true;
        }

renderShape() is called during object creation to create a rendering of the shape. Most subclasses (except Scribble) override this method and create a display list appropriately.

        protected function renderShape():void {}

getSelectionShape() is called by ArtCanvas to retrieve a new shape that will be used to show that a given ArtShape is selected. It is common to show filled-rectangle handles on the corners of the bounding box of a shape, so that's what this superclass implements. Some subclasses (such as Line) may choose to override this method and return a different selection shape.

        public function getSelectionShape():Shape
        {
            var shape:Shape = new Shape();
            var bounds:Rectangle = getBounds(parent);
            shape.graphics.beginFill(0);
            shape.graphics.drawRect(
                    bounds.left - HANDLE_SIZE_HALF, 
                    bounds.top - HANDLE_SIZE_HALF,
                    HANDLE_SIZE, HANDLE_SIZE);
            shape.graphics.drawRect(
                    bounds.left - HANDLE_SIZE_HALF, 
                    bounds.bottom - HANDLE_SIZE_HALF,
                    HANDLE_SIZE, HANDLE_SIZE);
            shape.graphics.drawRect(
                    bounds.right - HANDLE_SIZE_HALF, 
                    bounds.bottom - HANDLE_SIZE_HALF,
                    HANDLE_SIZE, HANDLE_SIZE);
            shape.graphics.drawRect(
                    bounds.right - HANDLE_SIZE_HALF, 
                    bounds.top - HANDLE_SIZE_HALF,
                    HANDLE_SIZE, HANDLE_SIZE);
            shape.graphics.endFill();
            return shape;
        }

Now, let's take a look at the ways that some of the subclasses build upon the capabilities of ArtShape.

ArtShape Subclasses

In Line.as, we override both the renderShape() method and the getSelectionShape() method. getSelectionShape() is not terribly interesting; it simply draws two filled rectangles at the endpoints of the line. But renderShape() is where we build the display list to render the line with the current endpoints:

        override protected function renderShape():void
        {
            graphics.clear();
            graphics.lineStyle(strokeWidth, color);
            graphics.moveTo(startPoint.x, startPoint.y);
            graphics.lineTo(endPoint.x, endPoint.y);
        }

Ellipse is even simpler, since it does not override getSelectionShape() (it uses the ArtShape implementation to draw the selection handles on the bounding box corners). It has one additional item in the constructor, to determine whether the ellipse is filled or not:

        public function Ellipse(width:int, color:uint, filled:Boolean)
        {
            super(width, color);
            this.filled = filled;
        }

Ellipse (and the other filled shape subclass, Rect) uses this filled variable to determine whether to fill or stroke the shape in the renderShape() method:

        override protected function renderShape():void
        {
            graphics.clear();
            if (filled) {
                graphics.beginFill(color);
            } else {
                graphics.lineStyle(strokeWidth, color);
            }
            graphics.drawEllipse(startPoint.x, startPoint.y,
                    endPoint.x - startPoint.x, endPoint.y - startPoint.y);
        }

Most of the shapes are similarly simple, depending on ArtShape for most functionality. The exception, as you might have guessed from the plethora of parenthetical mentions of it above, is Scribble.

Scribble.as

This shape is different than the others because it does not simply draw itself between the start and end points of the mouse drag, but rather adds a new line segment for every new mouse position during the drag.To do this, it creates the display list dynamically, starting it at mouse-down and adding to it during every drag operation. This means that there is more happening during the actual point-adding methods, but that renderShape() is noop'd (because the display list has already been created).

Here is the start() method, where we begin the display list based on the first mouse location:

        override public function start(value:Point):void
        {
            points.addItem(value);
            graphics.lineStyle(strokeWidth, color);
            graphics.moveTo(value.x, value.y);
        }

Note that we also cache, here and in the other dragging methods, the intermediate points in our internal points object; this is for later use in detecting whether a shape is valid. It would also be useful if we allowed advanced editing of the individual points of a Scribble object. But that's a feature for another day...

Subsequent calls to drag() and the final addPoint() method add lines to the display list, which are drawn from the previous point in the display list (either the first moveTo() point for a new line or the point in the last lineTo() operation):

        override public function drag(value:Point):void
        {
            points.addItem(value);
            graphics.lineTo(value.x, value.y);
        }

Scribble's other tweak on ArtShape is an override of validShape(). It's not good enough to detect whether the start and end points are coincident, as ArtShape does; we need to walk the entire list of our cached points in a Scribble to see whether they are all the same:

        override public function validShape():Boolean
        {
            var firstPoint:Point = Point(points.getItemAt(0));
            for (var i:int = 1; i < points.length; ++i)
            {
                if (!firstPoint.equals(Point(points.getItemAt(i)))) {
                    return true;
                }
            }
            return false;
        }

Finally!

That's it. The whole application. We saw bits of nearly every class in the source base, only skipping those too simple to be interesting or where the functionality was similar to code already shown. But I encourage you to download the source tree, look at all of the files, build it, run it, and play with it. And if you discover any problems, let me know; I'll fix them and update the project. (This doesn't include major feature updates; I purposely kept the project small so that I could easily show various features of Flex, Flash, ActionScript3, and MXML. Sure, I'd love to write a 3D modeling tool, but that's not happening in TopDrawer).

A future installment will address some of the design aspects that I called out along the way. The current code is not bad, but there are definitely some improvements that could make the code and application just a tad nicer.

I look forward to writing more of these kinds of articles. I still have a big learning curve ahead of me with the full stack of Flash, Flex, and AIR, and I'll be writing applications to teach myself how things work. And I'll be posting that code here so that you can learn right along with me.


Friday, March 14, 2008

Top Drawer, Part II

Q: Why do graphics geeks write vector art applications? A: We're just drawn to it.

Q: Why couldn't the blank canvas get a date? A: He didn't have any good lines.

Q: Why did the rectangle go to the hospital? A: Because it had a stroke.

Q: Why is TopDrawer sluggish when the canvas is empty? A: Because it's out of Shapes.

Welcome to the second installment of TopDrawer, a series in which I describe how a simple vector-drawing application was implemented in Flex.

Here's the application again, in case you missed it last time:

In Part I, we went through the GUI layer of the application, stepping through most of the code in TopDrawer.mxml. This, time, we'll take a look at the ActionScript3 class ArtCanvas.as, which is where most of the application logic for TopDrawer is found, including the mouse input that handles creating and editing the shapes on the canvas.

ArtCanvas.as

The source code for this file can be found here; you may find it helpful to download it and view it in a separate window, as I'll only be showing snippets from the file below as we walk through it.

ArtCanvas is a custom component, a subclass of UIComponent, which is the base class of all Flex components (and the class which you might usually want to subclass for custom components of your own if you don't need any of the specific capabilities of the existing subclasses). The ArtCanvas component is the visual area in which the shapes are drawn and edited. It handles both the display of the objects (through other classes that we'll see later) as well as the mouse and keyboard events that drive the creation and manipulation of the shapes.

Architecture

Functionality

The functionality of ArtCanvas is fairly simple:

  • Properties: The properties of ArtCanvas determine the type of shape being drawn, the properties of that shape, and the gridSnap properties that affect how drawing occurs.
  • Creation: Through handling mouse events, ArtCanvas creates shapes as the user drags the mouse around on the canvas.
  • Selection: Mouse events also result in selecting and deselecting objects for editing.
  • Editing: This is perhaps an optimistic term for what the user can do with the shapes in this very simplistic application, but selected shapes can be either moved (through mouse dragging) or deleted (through keyboard events or clicking on the Clear button).
  • Rendering: The canvas has a simple white background that ArtCanvas creates with a small display list. All other rendering (of the shapes on the canvas) is handled by the shape objects themselves.

Standard Overrides

A custom component like ArtCanvas that subclasses directly from UIComponent will typically override the following four methods:

        override protected function createChildren():void   
        override protected function commitProperties():void
        override protected function measure():void
        override protected function updateDisplayList(unscaledWidth:Number,
                unscaledHeight:Number):void

These methods are all called during specific phases during the lifecycle of any component and are the appropriate places to perform certain operations for the component:

  • createChildren(): This method is called during the creation phase of the component. If the component is a container for other child components, this is a good place to create them. Note, however, that if children of this component are created dynamically, or otherwise not a permanent feature of this component, then it is not required to create them here. For example, in our case the drawing shapes will be children of the canvas, but none yet exist when the canvas is created, so there is no need to create any children at this stage. So ArtCanvas does not bother to override this method.
  • commitProperties(): This method is called prior to updating the rendering of this component during a frame, if any properties have changed. Imagine a component with several properties, some of which might cause a significant amount of work to update the state of the component. In this case, it might be advantageous to update the state of a component all at once, based on the cumulative effect of all property changes since the last frame. In the case of our simple canvas component there is no need for this, so once again we get away with not needing to override this method.
  • measure(): This method is called prior to rendering the component during a frame if anything has changed that may affect the size of your component, to make sure that Flex knows the preferred size of your component. Some components may base their display size on some internal state, such as the amount of text within a button, or the size of an image, or other factors which the Flex rendering system may not know how to calculate for you. In these cases, your component needs to set some internal UIComponent sizing values. Yet again, we have work here for ArtCanvas; the size of the canvas is completely defined by the container of the canvas (which Flex controls), so we have no preferred size to communicate to Flex and do not need to override this method.
  • updateDisplayList(): This method is called prior to rendering the component during a frame, when the component needs to change the way it is being drawn. Note that because Flash uses a display list to render objects, this method will only be called when the rendering of an object needs to be updated. During normal frame processing, Flash already knows how to draw the component. We actually do have some custom rendering for our component, so we did not escape this time and we do override this method and draw our component accordingly. We'll see this simple code below.

Event Handling

Objects can choose to listen to events that occur in the system, such as mouse and keyboard events on components. For any events that they want to handle, they call addEventListener() and point to a function that will be called when any of those events occur. In the case of ArtCanvas, we handle both mouse events (on the canvas itselft) and keyboard events (in the overall application window). For any event, we can get information from the Event object to find out what we need: x/y information for mouse events, keyboard characters typed for keyboard events, and so on.

The Code

Now that we understand what ArtCanvas is trying to do, let's step through the interesting parts of the code of ArtCanvas.

We Got Style

There are a couple of styles used by ArtCanvas, which are declared outside of the class:

    [Style(name="gridSize", type="int", format="int", inherit="no")]
    [Style(name="gridSnap", type="Boolean", format="Boolean", inherit="no")]

These styles control an invisible grid on the canvas that the cursor will "snap" to during drawing. This feature can be useful for making more exact drawings where objects need to line up and typical mouse movement precision makes that difficult. These metadata tags ("[Style...]") communicate to Flex that this class can be styled in MXML to affect these properties. Now, developers can use CSS style tags to change the values of these grid properties, as we saw previously in the code for TopDrawer.mxml.

We retrieve the values for these properties dynamically as we track the mouse:

        private function getGridSize():int
        {
            return (getStyle("gridSize") == undefined) ? 10 : getStyle("gridSize");
        }   

        private function getGridSnap():Boolean
        {
            return (getStyle("gridSnap") == undefined) ? false : getStyle("gridSnap");
        } 

And we use these values when we call snapPoint(), which is responsible for returning the nearest point on the snap grid to a given (x, y) location (which will typically just be the current mouse location). I won't bother putting the code to snapPoint() here, since it's pretty simple; just trust me that it does what I said, and check out the code in the file for the details.

A: Is it difficult aligning objects in TopDrawer? A: No, it's a snap

Events

We create a custom event in ArtCanvas, and use the following metadata to communicate this to Flex:

        [Event(name="drawingModeChange", type="events.DrawingModeEvent")]

When the internal variable currentShapeMode changes, we will dispatch the drawingModeChange event so that listeners (which are declared in TopDrawer.mxml, as we saw last time) are aware of the change.

Properties

Now, let's see the instance variables that will be used for the canvas:

        // The drawing shapes on the canvas
        private var shapes:ArrayCollection = new ArrayCollection();

        // Point at which a shape starts being drawn - used to track
        // delta movement in mouse drag operations
        private var startPoint:Point;

        // Current shape drawing mode
        private var _currentShapeMode:int = LINE;
   
        // Current shape being drawn
        private var currentShape:ArtShape;
   
        // Currently selected shape. If null, no shape is selected.
        private var selectedShape:ArtShape;
   
        // Shape which renders selection handles for currently selected shape
        private var selectionShape:Shape;
   
        // Color with which following primitives will be created and drawn
        private var _drawingColor:uint;
   
        // Stroke width for future line-drawn primitives
        private var _strokeWidth:int;

Some of these are used to track internal state, such as the startPoint of the current ArtShape. Others are values that are controlled through the GUI, such as the drawingColor, which can be changed by the user via the ColorPicker component. It is helpful, at least if you're just learning ActionScript3, to see the pattern for these externally-modifiable properties in the language. Let's look at currentShapeMode as an example.

Note that our class variable for _currentShapeMode is private. But we want this variable to be settable from outside the class. In addition,we would like to dispatch our custom event drawingModeChanged when this variable changes. A typical pattern in other languages is to have a parallel "setter" method, such as setDrawingColor(), that can be called to affect the value of _currentShapeMode. A similar setter is created in ActionScript3 by the following code:

        public function set currentShapeMode(value:int):void
        {
            _currentShapeMode = value;
            dispatchEvent(new DrawingModeEvent(value));
        }

Note the syntax here; the method is not actually called "setCurrentShapeMode()", but instead uses the keyword "set" to indicate that it is a setter for the class variable currentShapeMode. The cool thing about this approach is that external users of this variable simply reference currentShapeMode directly, instead of calling a method, like this:

        canvas.currentShapeMode = somemode;

For example, the DrawingShapeIcon component defined in TopDrawer.mxml that handles setting the drawing mode to LINE does this by the following click handler:

        click="canvas.currentShapeMode = ArtCanvas.LINE;"

This approach has the terseness of setting a field from the caller's standpoint, but actually calls your set method, where you can perform more than a simple assignment of the variable. In this case, we need to both set the value of the variable and dispatch an event; we do both of these in our setter.

Shapes

Finally, we're onto the heart of this class, and the entire application: creating and manipulating shapes. This is done mainly through mouse handling; mouse clicks allow selection and mouse drags enable either creation (if nothing is selected) or movement of a selected shape.

Since we will need to handle mouse events, we need to inform Flex that we want to listen for these events. We do this in our constructor:

        public function ArtCanvas()
        {
            super();
            addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
            addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
        } 

Of course, these events will only give us move up/down information. In order to handle mouse drag events we also need to track mouse movement. But since we only care about mouse movement while the mouse button is down (that is, mouse drags, not just mouse moves), we will only add a listener for mouse movement when there is a mouse down event. We'll see how that is done that later in the handleMouseDown() method.

First, a utility method. There are two different places where an object may be selected: when an existing shape is clicked on by the user and when the user finishes creating a shape. To avoid duplicating the code, there is a selectShape() method. This method registers that a shape is selected, creates a new selectionShape, which is basically a set of selection handles (filled rectangles), the position of which is determined by the shape being selected, and adds the selectionShape to the children of the canvas so that it is displayed appropriately:

        private function selectShape(shape:ArtShape):void
        {
            selectedShape = shape;
            selectionShape = selectedShape.getSelectionShape();
            addChild(selectionShape);
        }

Here is our handler for mouse down events. startPoint gets the point where the mouse was clicked on the canvas or, if grid snapping is enabled, the nearest point on the grid:

        private function handleMouseDown(event:MouseEvent):void
        {
            startPoint = snapPoint(event.localX, event.localY);

Next, we get the global mouse location relative to the Flex window (otherwise known as the Flash "stage"), which we will use in testing for object selection. Note that we do not use a grid-snapped point for selection because we want to test against the actual pixels of a shape, and many or most of those pixels will actually not be on the grid (picture a diagonal line, for example, most of whose pixels lie between, not on, grid points):

            var selectPoint:Point = localToGlobal(new Point(event.localX, event.localY));

Next, we see whether there is already a currently-selected shape. If so, we see whether we hit that shape with this mouse-down operation. If not, we deselect the shape (which includes removing the transient selectionShape from the children displayed by the canvas):

            if (selectedShape) {
                if (!selectedShape.hitTestPoint(selectPoint.x, selectPoint.y, true)) {
                    removeChild(selectionShape);
                    selectedShape = null;
                }
            }

If there is no shape selected (or if we deselected a previously selected shape because the current mouse location missed it), then see whether we should select a shape, based on the global mouse position. Note that the true parameter in the hitTestPoint() call tells the method to base hits only on actual shape pixels not the simple bounding box of the shape:

            if (!selectedShape) {
                for each (var shape:ArtShape in shapes)
                {
                    if (shape.hitTestPoint(selectPoint.x, selectPoint.y, true))
                    {
                        selectShape(shape);
                        break;
                    }
                }
            }

If we still do not have a selected shape, then the mouse truly didn't hit any of the existing shapes. So it's time to create a new a new shape. This is done by instantiating one of the handful of specific Shape subclasses, according to the currentShapeMode variable (the value of which is determined by which icon the user selected in the TopDrawer.mxml UI), sending in this initial point to the new shape, and adding that shape to the display list of the canvas:

            if (!selectedShape) {
                switch (currentShapeMode) {
                    case LINE:
                        currentShape = new Line(strokeWidth, drawingColor);
                        break;
                    case ELLIPSE:
                        currentShape = new Ellipse(strokeWidth, drawingColor, false);
                        break;
                    // and so on: other cases deleted for brevity
                }
                currentShape.start(startPoint);
                addChild(currentShape);
            }

Finally, we now need to track further mouse-move events, which will be used to either move a selected shape or continue creating the new one:

            addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
        }

As long as the mouse button is held down, we will receive mouse movement events, which will be handled as drag operations. If there is no currently selected object, then each drag operation sends another point into the in-creation shape. Otherwise, each drag moves the currently selected object (and its selectionShape) according to how much the mouse moved since the last mouse event:

        private function handleMouseMove(event:MouseEvent):void
        {
            var location:Point = snapPoint(event.localX, event.localY);
            if (!selectedShape) {
                currentShape.drag(location);
            } else {
                var deltaX:int = location.x - startPoint.x;
                var deltaY:int = location.y - startPoint.y;
                selectedShape.x += deltaX;
                selectedShape.y += deltaY;
                selectionShape.x += deltaX;
                selectionShape.y += deltaY;
                startPoint = location;
            }
        }

When the mouse button is released, we will receive that event in our handleMouseUp() method. In this method, we will only process position information for objects being created (selected objects being moved do not need a final operation to complete; they will be handled just by the previous drag events). We add the final point to the created shape, then test whether it is valid; this prevents spurious null objects where the first/last/intermediate points are all the same. If the shape is valid, we select it and add it to the current list of shapes for the canvas. Finally, we remove our mouse movement listener since we only care about movement for dragging between mouse up and down events:

        private function handleMouseUp(event:MouseEvent):void
        {
            if (!selectedShape) {
                if (currentShape.addPoint(snapPoint(event.localX, event.localY))) {
                    if (currentShape.validShape()) {
                        shapes.addItem(currentShape);
                        selectShape(currentShape);
                    } else {
                        removeChild(currentShape);
                    }
                    // done creating current shape
                    currentShape = null;
                }
            }
            removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);           
        }

There is one more event that we track, which is the key-down event from the keyboard. We do this just to allow easy deletion of the currently-selected object. Our handler simply deletes the current object from the list of shapes, removes it from the children of the canvas (which removes it from the objects being displayed by Flex), and removes the current selectionShape as well:

        public function handleKeyDown(event:KeyboardEvent):void
        {
            if (event.keyCode == Keyboard.DELETE) {
                if (selectedShape) {
                    var itemIndex:int = shapes.getItemIndex(selectedShape);
                    if (itemIndex >= 0)
                    {
                        shapes.removeItemAt(itemIndex);
                        removeChild(selectedShape);
                        selectedShape = null;
                        removeChild(selectionShape);
                        selectionShape = null;
                    }
                }
            }
        }

Similarly, clicking on the "clear" button in the UI will cause all objects in the list to be deleted by a call to the clear() method:

        public function clear():void
        {
            for each (var shape:Shape in shapes) {
                removeChild(shape);
            }
            shapes.removeAll();
            if (selectedShape) {
                selectedShape = null;
                removeChild(selectionShape);
            }
        }

Display

There's one final method that is interesting to look at: updateDisplayList(). We saw this method earlier in our discussion of the typical four overrides from UIComponent, where this is the only method that we actually need to override in ArtCanvas. In our case, all we have to do here is draw our canvas to look like what we want. Here, we set the fill to be solid white and fill a rectangle the size of our component:

        override protected function updateDisplayList(unscaledWidth:Number,
                unscaledHeight:Number):void
        {
            super.updateDisplayList(unscaledWidth, unscaledHeight);
            graphics.clear();
            graphics.beginFill(0xffffff, 1);
            graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
        }

Note that all of the interesting rendering, that of the shapes that have been and are being created, is handled by the shapes themselves; when they are added as children of ArtCanvas, Flex automatically works with those shapes directly to get their display lists. So all we need to do here was handle the rendering for the canvas itself.

goto end;

That's it for ArtCanvas, which has most of the logic in the entire TopDrawer application. I'm tempted to put the rest of the code here, but I'd like to keep each blog somewhat shorter than way too long, so I'll defer it to my next entry. In that next TopDrawer article (ooooh, I can feel the suspense building...), I'll go over the code in the Shape classes and other minor classes. I'll also post the full source tree so that you can build and play with it yourself.