|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.)|
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:
Now we can come over to the workspace and create a new instance of our new class. Type
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 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].
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.
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].
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.
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.
Since I added an instance variable, I'd like to know that it's initialized to something. Let's define this message for TestMorph.
"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:
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.
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.
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".
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.
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: