Tutorial: Fun with the Morphic Graphics System
by John Maloney
 
This tutorial is meant to be used in a Morphic Project. To create a Morphic Project simply hold the mouse button down on the Squeak window background, away from any other windows it contains, to get the screen menu. Select "open..." and then select "project (morphic)". A small orange "Unnamed" morphic project window appears. Click in the middle of it to zoom into the new Morphic Project. (If you are not sure where you are, just pop up the screen menu - if its title is "World", you are already in a Morphic Project.)
 
First we need to create a Browser and a Workspace. Just to review, hold down the mouse button on the blank Squeak screen. From the screen menu that appears, choose "open...". Click on "browser". A green browser will appear. Get the screen menu again and choose "open...", and choose "workspace".

If you came fresh from the previous tutorial, you'll already have a category called "My Stuff". Scroll to the bottom of the leftmost list in the browser. It should be there.

If not, we need to make a new place to add your stuff. In the upper left pane of the browser, use the right mouse button to get a menu [Option-click on a Macintosh computer - Mac commands will be presented like this in the rest of this tutorial]. Choose "add item..." and type "My Stuff" and hit Enter. Scroll down to the bottom of the list and you'll see it already selected.

Again we're going to start with nothing and build up a program. We're going to start with an empty class, but in this case we'll want it to be a subclass of Morph. Morph is the generic graphical class in the Morphic object system. We're looking at the template for creating a new class. Make it look like this:

Morph subclass: #TestMorph
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'My Stuff'
First select "Object" and type the word "Morph" with a capital M. The new class will be a subclass of Morph. Select "NameOfClass" and type in "TestMorph". Cut out the text of the instance variable names and the class variable names. Alt-s to accept [Command-s].

Now we can come over to the workspace and create a new instance of our new class. Type

TestMorph new openInWorld.
Select that line and hit Alt-d [Command-d] or choose "do It" from the right-button menu [Option-click].

Your new graphical object, a blue square, is in the upper left corner. You've got a graphical object and you didn't even write any code. You can pick this object up and move it around. Just grab it with the mouse. Notice how, when you picked up this Morphic object, it threw a dark shadow behind it. That helps you to see that you've actually lifted it up above everything else. If there were overlapping objects, grabbing this object would automatically pull it to the front, and the drop shadow would show that it's now in front of everything else.

You'll notice the odd capitalization of 'openInWorld'. We like things to read like English, but in Smalltalk a selector must be a single string without any spaces in it. So we have the convention of using capital letters at the beginning of each English word, and stitching them all together without spaces. Other languages often use underbars or dashes between the words.

Now Alt-click [Command-click] on this blue thing, and you will see a surrounding array of colored circles which we call a "halo". Each of these is a quick way to send a command to the Morph. If I linger over any of these dots with my cursor, a little help balloon will pop up telling me what the dot does. This black dot's balloon says: "Pick up". In this case, if I drag the black handle, it  does exactly the same thing as if I dragged the blue rectangle itself. That may seem like a useless function, but the reason it's there is that sometimes your Morphs will have an interactive behavior, like a button does. This handle gives you a way of directly manipulating something which has behavior associated with mouse clicks.

The halo on a Morphic object, with labels

The basic idea here is that most systems, such as HyperCard, resort to modes for changing the size of an object, etc. That is, separate modes for editing an object as opposed to using it. In Morphic, you can get a halo on anything regardless of how active it is. The halo is a safe way of dealing with it while it's running. You never have to stop it's button functions or anything else. If this thing were a button, you could edit it without activating it, and without turning it off.

Let's look at what the halo can do. For example this green thing makes a copy, and the X in the pink circle deletes it (moves it to trash). Another common thing to do is to change the size, so the yellow dot does that. Try not to do too many other things to it yet, especially don't use this blue button here (on the lower left of the object).

So let's start making this our own kind of object. Let's write some methods to customize it.

The first thing I want to do is make our object do something when you click on it with the mouse. There are two methods we have to implement. Click on the Browser. Click on "no messages" in the third pane. Paste this into the bottom pane and accept, Alt-s [Command-s].

handlesMouseDown: evt
    ^ true
If this is the first time I've accepted a method in this image, I must now type in my initials, which will be recorded along with the methods I change. This method, handlesMouseDown:, tells morphic that this is a mouse-sensitive thing.

The next method that we're going to add is what to actually do when the mouse goes down on it. Go ahead and paste this right over the other method.

mouseDown: evt
    self position: self position + (10 @ 0).
This will move the object to the right ten pixels when you click on it. Try clicking on the object and see what happens.

At this point we have two methods. One of them returns 'true' to say that we want to handle mouseDown: events. The second one is responds to the mouse-down event by changing the object's position..

Now I want to give this morph some behavior for redrawing itself. Paste this method into the browser and accept it by Alt-s [Command-s].

drawOn: aCanvas
    | colors |
    colors := Color wheel: 10.
    colors withIndexDo: [:c :i |
        aCanvas fillOval: (self bounds insetBy: self width // 25 * i + 1)
                color: c].
Now when you click on the object you get a totally different look for the object. Let's go back and look at what this method is doing.

The third line of the method (the first line of code) is assigning into the temporary variable 'colors'. If you see two vertical bars and a list of variable names, those are local variables to this method, often called temporary variables. In Smalltalk and Squeak you don't have declare locals -- the system will ask you if they are really locals. And we don't have any "types" so you don't have to declare a type. Any variable can hold any kind of object. So we've assigned to 'colors' the result of this expression (Color wheel: 10). To show what that does, select 'wheel:' and type Alt-m [Command-m] for immMMMMplementers. You will see that there are two implementations - select the one in Color class.

One thing we often do in Squeak is we put a little comment at the head of a method. The first line says what 'wheel:' does. It returns a collection of thisMany colors where thisMany is the argument. The colors are evenly spaced around the color wheel. It's a spectrum of colors.

When it's easy to give an example of the method in action, we do that too. The second comment here is an expression which you can actually execute. Select inside the quotes and Alt-d [Command-d]. Across the top of the screen it just splats out the color wheel. It's a collection of colors spaced around the spectrum. (But not total saturation and not total brightness, so they won't be too garish.)

This is a world in which we can scribble directly on the screen, but it does not get automatically cleaned up afterwards. So I'm going to use the "restore display" item on the screen menu to get rid of the stuff that got written on the top of the screen.

This was a digression to figure out what "wheel:" meant. We know now that it's a collection of Colors. Close the "Implementors of wheel:" window by clicking in the "X" on the left side of the title bar.

The next part of this method steps through that set of colors.

This is a fragment of the method drawOn:
    colors withIndexDo: [:c :i |
        aCanvas fillOval: (self bounds insetBy: self width // 25 * i + 1)
                color: c].
The withIndexDo: message supplies both an element of colors, "c", and an index, "i", to keep track of where we are in the list. The index will be one the first time around, two the second, and so on, all the way through to ten. We need to know where we are in the list to increase the inset, making the ovals ever smaller. We're sending the message, "fillOval:color:" with the argument in parens -- which is a Rectangle inset by (((self width) // 25) * i + 1) pixels inside the outer bounding Rectangle returned by (self bounds) -- and a second argument which is the color. As the index, i, increases, the inset also increases, returning ever smaller nested Rectangles, in each of which we inscribe an oval and fill it with the color, c.

When we look at our object, we see up to ten bands of color. It's pretty cool, and because this is using the result of the message "width" being sent to itself, as one of its parameters, it actually knows how big it is. Bring up the halo by Alt-click [Command-click] and drag on the yellow dot. So as you resize it, it just keeps redrawing itself at whatever size it is.

The next thing I want to do is make clicking on it actually invoke some kind of animation. So that the first thing to do is to create a list of points. When I click on the object, I want it to move to each point in turn until I run out of points to go to. For that, I need a bit of state in my object.

Morph subclass: #TestMorph
    instanceVariableNames: 'path'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'My Stuff'
Go back and click in the Browser, and then on the "instance" button. That will bring up the TestMorph class definition. Then click in the place for instance variable names, between the single quotes, and type "path". That will be the name of the new instance variable holding a list of points of where to display our object. Now I'm going to click on the message category whose name has changed to "as yet unclassified".

Since I added an instance variable, I'd like to know that it's initialized to something. Let's define this message for TestMorph.

initialize
    super initialize.
    path := OrderedCollection new.
Why is the line "super initialize" here? Even though the definition of class TestMorph looks very simple, path is not the only instance variable it has. In the second pane of the Browser, right-click [Option-click] to get a menu. Choose "inst var refs", you see a list of the other variables it has from superclasses. I'm not going to select any of them. If I did it would show me all the methods that use that instance variable, but you can see there are six instance variables inherited from class Morph.

"super initialize" sends initialize to my superclass, class Morph. When you send the message "initialize" to the receiver "super" it's really sending it to "self", but making sure that the inherited version of initialize gets invoked instead.

The guts of our initialize is "path := OrderedCollection new". This puts an empty Collection into path. Now when my test morph gets sent initialize, it first does all the initialization that Morph would do, and then it does it's own initialization. The result is that all those instance variables inherited from class Morph will be initialized plus the new one that I added.

We are actually using a convention here, since a new object is not normally sent the "initialize" message automatically. But, Morph always sends initialize to each new Morph instance.

This TestMorph, here on the screen, did not get initialized with our new code. We made it before we defined our version of initialize. Let's delete it and make a new one. Bring up the object's halo by Alt-click [Command-click]. Delete your object by clicking in the pink circle with the X in it. It is moved to the trash can. Now get a new TestMorph (properly initialized this time) by executing:

TestMorph new openInWorld.
We're going to make our object do something different when you click the mouse on it. We've got the Collection in "path". Copy in and accept this method.
startAnimation
    path := OrderedCollection new.
    0 to: 9 do: [:i | path add: self position + (0@(10 * i))].
    path := path, path reversed.
    self startStepping.
We are creating a new OrderedCollection in line one. In the next line, we're doing something ten times and adding the result of some expression to the variable path. We are saving points that are related to the current position, "self position", plus a point that I constructed. The constructed point's X-coordinate is zero, and after the at-sign "@" (which constructs a point), the Y-coordinate is ten times "i", where "i" is the argument to the loop, which steps from zero to nine by increments of one. So, the end effect will be to create ten points which offset the Y-coordinate of the current position by (ten times zero) to (ten times nine) and add each of these new points to the path.

In the next line, we expand the path to be the (path, path reverse). "reverse" takes any collection and puts it in reverse order. The comma (",") operator is just another message selector which concatenates two collections.

What we've done is taken our list of ten points which are going from zero to ninety, and added on a list that goes from ninety back to zero. So now we've got twenty points that start out and end up at the original position, and go to a bunch of points in between.

The last line of this method basically enrolls our morph in an engine that keeps causing it to be sent a message called "step". So this is the tick of the animation engine, the heart beat of the animation. Now we'll must make our morph understand the step method.

step
    path size > 0 ifTrue: [self position: path removeFirst].
This one is pretty easy to understand. It says that as long as there is something in that list of points, that is, as long as the size of the path is greater than zero, then move myself. Send myself the "position:" message with the argument which is the first point removed from the collection of points. It checks to make sure that there is something to be removed before it does each one.

Graphically, it will jump to the next point instantly and you'll see the change on the next frame of the animation.

All of this is now set up to be running, but, it's not animating. Well, that's because we haven't actually kicked it off by sending it a message to the start animation. One thing I could do is I could try to figure out a way to send that message to the object, but an easier thing to do is to make it send itself the message when the mouse button goes down.

mouseDown: evt
    self startAnimation.
There are two ways you can define this. You can paste it over any method showing in the bottom pane of the Browser. Or you can click on "mouseDown:" in the right pane of the Browser, and modify our existing mouseDown:. The same thing happens either way when you accept.

When you've done that, go over and click on your morph, and you should see something happen. It's animating, which is great, but it's going very, very slowly. Gee, I'm disappointed because I thought that Squeak was faster than that. What is actually happening is that "step" is only sent once every second to an object by default. But the object can decide how often it wants to be told to "step" by implementing a message "stepTime".

stepTime
    ^ 50
This returns the time in milliseconds between step messages that the object is requesting. Now, obviously you can ask for zero time between step messages, but you won't get it. This is basically an assertion of how often you would like to be stepped with this animation.

Now if we go over here and click on our object, we get a much faster animation. I'm going to change it to ten milliseconds, and try to get a hundred frames a second. Let's see if that works. Well, I don't know if I'm really getting a hundred frames a second, but it certainly is zippy.

As you know, I work for "the animation company". This animation starts and stops instantly, and everyone knows that "slow in" and "slow out" are needed to make it look really good. Here is a modification that uses the square of "i" to start the motion off slowly.

startAnimation
    path := OrderedCollection new.
    0 to: 29 do: [:i | path add: self position + (0@(i squared / 5.0))].
    path := path, path reversed.
    self startStepping.
I only changed one line in this method. We're going to make thirty points in the path, and then append the reverse version to make sixty points. The other change is that instead of saying times ten, I'm saying "i squared divided by five". At the end, the expression will be 841 divided by 5.

You get an animation that starts down slowly and speeds up and then slows down again as it comes back up. In fact, it looks kind of like a ball bouncing.

Now would be a good time to play with some pre-made Morphic objects on your own. Let's get a parts bin full of objects. If you have tabs enabled in your system, click on the "Supplies" tab at the bottom of the screen. If there is no tab at the bottom of the screen, use the screen menu and choose "authoring tools...". Choose "standard parts bin". In either case you have a bunch of objects you can drag onto the screen. After you have dragged one out, Alt-click [Command-click] to get a halo, and hold down on the red circle to get the object's content menu. Here are some of the Morphs to play with: