Morphic Animation III
Last updated at 8:19 pm UTC on 5 October 2005
As if the last section wasn't enough, here's another blow to the head. You'll just have to believe me, I don't like wielding this kind of power. I don't like summoning the power of Morphic and making all the people who stood in my way during my ascent to the top pay. No, I don't like climbing through the pile of bodies that litter the streets. Oh no, I don't enjoy choking on the ashes of my enemies to whom I've taught the ultimate lesson. Oh sorry. Do I sound bitter?
Remember back in happier times when the world was simple and we built our first little window launcher app? In LaunchAlignmentMorph #initialize we initialized our launch buttons with several statements. Then I just kind of waived my hands and said, "Oh that's easy, don't pay it any attention".
You should have been looking. Looking real hard. I'll share another secret here. Whenever you are watching a magic trick, the magician will always perform some type of misdirection so that you can't figure out how the trick is done. Rule number one in trying to figure out how a magic trick is performed is to watch for misdirection cues.
Let's look at one of those button initializations again.
button _ LaunchButtonMorph new
label: 'Browser' ;
target: ( World activeHand ) ;
actionSelector: #openBrowser ;
First, we label the button 'Browser'. That's the name of the button that shows up on the screen. The next three statements set instance variables of LaunchButtonMorph. Fair enough, but then what happens?
If we look at SimpleButtonMorph #doButtonAction which is the behavior that LaunchButtonMorph inherits we see:
"Perform the action of this button. Subclasses may override this method. The default
behavior is to send the button's actionSelector to its target object with its arguments."
(target ~~ nil and: [actionSelector ~~ nil]) ifTrue: [
Cursor normal showWhile: [
target perform: actionSelector withArguments: arguments]].
What kind of gibberish is this? The key is the #perform:withArguments: message. What our buttons initialization said was, "when I receive a #buttonUp message, trigger the #doButtonAction event". Remember, our friend the HandMorph is reponsible for passing the #buttonUp message to us. The #doButtonAction says, "send the message #openWorkspace to World activeHand". We now know that the World activeHand is an instance of the manly HandMorph, so #perform:withArguments: is a way of saying, "World activeHand openWorkspace" using variables instead of a direct reference to the actual object and selector. So when we press the Workspace button on the launcher, a Workspace opens.
I'm telling you, this is extreme. It's thinking time now. This seems like a whole lot of work just to open up a Workspace window. Why go through all that work?
Guess what? Computer weenies have a name for this. That's a visual feedback clue for you, an alert that something unpleasant is about to happen. They call it dynamic dispatching. The target and actionSelector instance variables are, well, variable. That means they can change. And change during runtime. They are what we call dynamic. We know that the dispatching part means to send messages.
In other words, after the button is created and alive, you can change the target and actionSelector, and the button will exhibit different behavior. For example, execute this in a Workspace:
bo _ LaunchAlignmentMorph openLauncher.
When you press the buton labeled 'Browser' a Code Browser appears on the screen. Now execute:
bo firstSubmorph label: 'Explore World'; target: World; actionSelector: #explore.
You notice that the 'Browser' label changes to 'Explore World'. Click the 'Explore World' button. "Explorer open captain!" Exact same button, same structure, different behaviour when you click on it. One line of code! As the eloquent actor Keanu Reeves would say, "WHOA!!"
This is just the tip of the iceberg. After you think about it awhile, you'll realize that you can create extremely complex behaviours using this technique. Remember those properties we were talking about? Well, you can fill up those big pockets with all sort of different types of behaviours and information, all with different names, all at your beck and call. At run time!!! Plus you aren't just limited to storing simple objects or selectors in there. Nope, you can store actual blocks of code, too. All of a sudden, YOU have a nice sized bag of tricks.
As a side note, people use properties a lot because they are dynamic, that is, they can grow and shrink over time. When an object is created, it has a set number of instance variables. Except in rare circumstances, the number of instance variables stays constant over the lifetime of that object. Properties are implemented like a list, so you can grow or shrink that list to your little hearts desire.
You'll see a lot of code where instance variables are used, and other parts where properties are used. Most of the time it's just the personal preference of the person writing the code. You could have just as easily coded up the behaviour of LaunchButtonMorph using an instance variable instead of properties for the target of the message send. I chose a property just so you'd get the chance to see what one looks like out in the hard, cold world.
I'll add that this functionality is as much due to the power of Smalltalk as Morphic. I'm telling ya, weapons don't get bigger than this in computerdom. If any of your computer friends hassle you about your interest in Squeak and Morphic, just whip this club out and beat them over the head with it. They'll have no defense unless they are some of those Lisp guys. Those people are very powerful. They always seem to have some parens to spare. But they don't have Morphic. And then again, those types of people aren't really your friends, are they?
Don't smite your enemies all at once with this kind of power. It's much better to take just one out and let all the rest cower in fear. Take it from me, I learned the hard way. And I hope that Johnny character didn't get this far.
On to Eye Candy
The AlignmentMorph may be replaced by any Morph class