Thursday, April 19, 2001

MIAW events falling through to the stage

I managed to fix yesterday's problem with capturing keydowns from a MIAW. To recap, I want to receive keydowns from a MIAW at the stage level, but I don't control the source code for the MIAW. What's a guy to do?


It turns out that if a keydownscript issues a pass command, then the keydown will automatically fall through to the stage, unless it hits and is absorbed by an editable field or text member. This is the ideal situation, since keydowns in fields are always unambiguous targets, while any others are ambiguous as to which window they may apply to. The question, then, is how to achieve this situation when you're programming the stage and you don't want to have to make changes to the MIAW code.


The answer, as is usual with Director, is to cheat.


The idea is to intervene in the MIAW and force it to exhibit this behavior - i.e., force it to issue a pass if the user isn't editing a text field in the MIAW. We do this by modifying (not setting) the MIAW's keydownscript. In a prepareframe handler, we watch the MIAW's keydownscript, and if we haven't already intervened, we insert code into it to get it to do what we want. Here's the code:

  on prepareMovie
tell window("spawned")
if (the keydownscript contains "--hook inserted" = false) then
the keydownscript = "--hook inserted" & return & /
"if (the keyboardFocusSprite > 0) then " & return & /
the keydownscript & return & /
"end if" & return & /
"pass"
end if
end tell
end prepareMovie


So, if the original keydownScript was simply a dosomething(), this code would change it to:


-- hook inserted
if (the keyboardFocusSprite > 0) then
dosomething()
end if
pass


As a result, we trap all keyboard events not directly handled by either an editable field or the function defined by the MIAW's author.

Wednesday, April 18, 2001

Windowing conundrum

Today I was working on a project that requires both the stage and a MIAW to respond to keys being pressed by the user. I don't control the code to the MIAW; it is being provided from another source. I have been attempting to find a way to manage keypress events so that the stage can respond appropriately to them, even if the MIAW is unaware of the context it is being run under.



Unfortunately, there appears to be absolutely no reliable way to give a window or the stage keyboard focus, nor is there any way to determine what window currently has the keyboard focus. About all you can do is trick the user into clicking in the window you want to have keyboard focus.



The only simple way to be sure that one window or another has keyboard focus is to ask each window what its keyboardFocusSprite value is. If an editable text member or a field happens to be being edited in one of the movies, it will return a value other than -1. However, there are many contexts in which you need to accept keystrokes outside of an editable field (such as controlling a character in a game with the arrow keys, or accepting keyboard shortcuts for an editor).



Another (riskier) approach would be to have the stage interfere with the workings of the MIAW by monkeying with its keydownScript and keyUpScript. If the MIAW's do not define these values, then it should not cause a problem. Simply issue a 'pass' in the script to allow normal operation. However, if the MIAW itself needs to define these scripts, then you are in danger of either breaking the MIAW's operation or losing events.



Clearly, the stage has a unique position in relation to MIAW's. It is considered the 'main' movie, the 'controller' movie, from which all MIAW's are spawned and manipulated. But if it is to act as a puppeteer, then it needs to have strings into those puppets. It should be able to insert a hook to get first crack at any input events. Unfortunately, there is no way to do this, and we have to rely on MIAW's to play nice and send important messages down to the stage. When you don't control the code for the MIAW's, this is difficult at best.

Tuesday, April 17, 2001

Startup / Shutdown dichotomy

When a Director movie begins playing, it goes through a series of initialization routines, in different contexts, before the normal operation of the movie continues. The first thing Director does is call a handler called prepareMovie. It then initializes all the sprites, and their beginSprite methods are called. Once the engine is all set up, Director callled startMovie.



This provides the necessary separation of initialization routines that allow us to set up code structures which the sprites need access to, and for starting up code structures that depend on sprites (and their behaviors) having registered themselves. This is all fine and good.



Director is not so good at shutting down, however. You would expect that there would be a parallel structure for when we want to shut down a movie: a stopMovie call, endSprite calls, and...what? There is no equivalent for prepareMovie method that is called after all the endSprites. If you initialize some code structure that supports sprites, which sprites need to reference as they shut themselves down, you're out of luck.



To provide a real example, suppose you wanted to write a program that uses FileIO to write out a log of everything your sprites do during a movie, say for debugging. You would initialize the FileIO xtra in preparemovie, and open the file. You could then write out sprite startup messages to the file in the beginSprite handlers. But when you go to shut down, you can't write out messages in the endSprite handlers if you shut down the xtra in the stopMovie handler, because that will execute before the endSprite handlers. And there is no handler that will execute after the endSprite handlers. You are forced into the inelegant solution of jumping to a blank frame in the stopMovie handler, which will trigger the endSprite handlers, and then do your cleanup.



This lack of cleanup options extends beyond this single issue, however. Director allows you to specify code to execute when an object is created (using the new command) in the standard way one might use a constructor in C++ or Java. But there is no destructor handler for objects. You cannot define code to be called when your object gets purged from memory - you must track all that manually and call a predefined method in your object when you delete it.



It would be nice to get some features that allow us to shut things down as elegantly as we start them up.

Monday, April 16, 2001

Text members vs. Field members

Today, I grappled again with the frustrating differences between the ways Macromedia Director deals with text members and field members.



Specifically, what I needed to do was generate an image object out of text, which had to be cropped close. I thought I would be able to use the charPosToLoc() command to find out where to crop the member's image from to get the desired picture, but it turns out that charPosToLoc() was only available for fields.



Unfortunately, you can only grab the image of a text member, not a field, so that approach leads to an impasse.



The final solution was less than elegant. I ended up having to trim the white space of the text member, a less than ideal situation because there's no elegant way to maintain your text baseline when you do that.



If there's any Macromedia people reading, please make some of the functionality between text members and field members cross over, or better yet, consolidate the two into one member type. It seems like every time I have to deal with text, I have to choose some feature to drop (or fudge) because each member type only supports half the features it should.