- HandMorph (the cursor)
- PasteUpMorph which uses WorldState
Other Useful Classes
- A SystemWindow implements a toplevel window that can contain other morphs, typically PluggableMorphs.
- To implement menus, you don't need morphic but you can use the PopupMenu class and its friends.
- AlignmentMorph can be used to layout other morphs; many will prefer to use the builtin layout support in SystemWindows.
- You can add Balloon Help to any morph.
A Morph of rectangular shape on which other morphs may be pasted. They stick as the PasteUpMorph instance is moved around.
The pages of a BookMorph are PasteUpMorphs.
A PasteUpMorph can be pasted on other PasteUpMorphs.
A reference to the topmost instance of PasteUpMorph is kept in the global variable World. This instance of a PasteUpMorph then has a WorldState and MorphWorldView.
HandMorph is the class representing a mouse cursor.
Morphic supports more than one hand, even hands controlled by other systems, called remote hands, or controlled by an event recorder, a HandMorphForReplay thingy. Normally, there's one primaryHand per World (kept in ActiveHand) and one activeHand during event processing. There's also the notion of a currentHand but I think, either primary or current but not both makes sense. A hand morph has some 30 instance variables - most of them results of poor refactorings. They hold internal states like time between double clicks or the active popup menu.
Each hand is an event source and you can register yourself for receiving events like mouse down or mouse moved. Normally, the hand automatically interacts with morphs so you don't need to register event handlers. Each hand also knows a keyboard focus, a morph that receives keyStroke events.
Each hand - normally represented by the hardware cursor - can switch to any form it wants and can bear user initials which helps to distinguish different mouse cursors. Actually all images but the primary hand are simulated. There are more instance variables to cache form images and to record damage done by morphs with which to redisplay theirselves while picked up.
Then a hand can send all events via a socket connection to some other remote hand. This code which burdens the already not trivial implementation, should be better factored out.
Finally, a hand controls tool tips (aka bubble help), popup menus and drag and drop. The hand also synthesizes the events it gets from polling during a world display loop.
There is a method category 'World menu' which implements the menus.
Class Morph is subclass of Object (used in Morphic)
Morph is the base class of everything that can be displayed in a World and of what you can interact via a hand. This is an intuitive notion, and the pragmatics have several angles.
- Creating a Morph
- Morphic Bounds - morphs have a size and shape
- Morph Structure - morphs can be nested within each other
- World - the topmost morph containing all the other morphs
- Balloon Help
- Morphic Step - morphs can be triggered periodically
- display - morphs know how to draw themselves
- input events - morphs can respond to the mouse and the keyboard
- drag and drop - morphs can dropped on top of each other
- Morph mouse up action (Smalltalk expression)
- Morph menus
- Morph class hierarchy
- Morph haloClass
If we look at the instance variables, Morphs have bounds and can contain other morphs as submorphs. The morph's own bounds merged with the submorphs' bounds is called fullBounds.
As each morph can be a submorph of another morph, it has an owner.
Every morph is directly or indirectly owned by the World. The only exceptions are morphs picked up by the hand. Their owner is a hand which is not part of the world.
Finally, each morph has a color and other attributes which are modelled by the MorphExtension helper class.
Creating a Morph
You can create a morph by choosing the menu item 'new morph...', or by using the object tool or by dragging a morph from the Flaps.
You can also create morphs from code. When you create a Morph in a Workspace, for example:
it does not display. To display the morph, send it the message openInWorld:
RectangleMorph new openInWorld
This adds the Morph to the World.
Some Morphs have special creator methods. See, for example, SketchMorph or ThumbnailMorph.
(SketchMorph fromFile: 'MS.jpeg') openInWorld
(SketchMorph fromFile: 'STvsJAVA.jpeg') openInWorld
player _ MPEGDisplayMorph new openInWorld.
player openFileNamed: '/home/janousek/.squeak/AlienSong.mpg'.
"player openFileNamed: '/home/janousek/.squeak/st80-adela-1982.mpg'."
Every morph has, to a first approximation, a rectangular shape and position. This is summarized by the
method, and can be changed with
gives the point at the top-left corner of a morph, and
returns a point encoding the width and height of the morph.
There are also several convenience methods such as
#topRight, and so on.
The World in Morphic is the rectangular area which fills the whole screen.
Technically it is a global variable which keeps the instance of the topmost PasteUpMorph in the Project. All other morphs are submorphs (or submorphs of submorphs) of this Morph.
Any Morph answers the message 'world' with this topmost instance of PasteUpMorph.
So all Morphs you see on a screen belong to one big composition hierarchy with the global variable World pointing to the topmost Morph, which is a PasteUpMorph.
To get an array with the components of a Morph you send it the message 'submorphs'.
A red-click on the instance of PasteUpMorph kept in World brings up an instance of TheWorldMenu.
Also see How to apply some code to all submorphs of a Morph
To add balloon help to a Morph, use the #setBalloonText: method.
Try the following example to see how it works:
World currentHand attachMorph: (RectangleMorph new setBalloonText: 'Testing').
If a morph implements a method
then that method will be called once every second (XXX is this the default?) as long as it is open in an active morphic world. To change the update period, modify the method
to return a different number of seconds.
This has been extended recently in Squeak 3.1. Anyone care to describe the extensions? Nowadays, you can have multiple step timers...
One twist on
is that the model of a SystemWindow can also be stepped. The relevant methods are
The primary use of
is probably to make animated morphs which update their appearance periodically. However, it is a general mechanism that allows some interesting designs. In particular, anywhere that a thread would normally be used, one can consider polling from
instead. The downside is that there is more latency between polls; the upside is that the method has no synchronization worries, and in particular it can freely make changes to the current morphic world.
Every morph may have an owner, and may have multiple submorphs. The ultimate owner is World, which is the morph that is directly displayed by the main morphic interaction loop. Cycles are not allowed in the submorphs relationship, and so the morphs form a tree with World at the root and submorphs underneath it.
A morph is added as a submorph of another morph using
or one of its variants such as
A morph is deleted from its current owner using
Finally, owners and submorphs may be queried using
To open a new morph initially, use
A SystemWindow implements a window that can contain other morphs, typically PluggableMorphs. A typical simple example is the Inspector window which contains one PluggableListMorph and two PluggableTextMorphs.
To see how it is constructed look:
containing a TwoWayScrollPane
containing a PasteUpMorph
The mechanism for arranging submorphs has changed in Squeak 3.x. Every Morph has now the capability to layout its submorphs (for details see class category 'Morphic-Layout' in the image), so you will generally not need to use AlignmentMorph anymore. (Although it isn't quite deprecated.)
addMorph: (EllipseMorph new extent: 40@40; color: Color red);
addMorph: (EllipseMorph new extent: 50@50; color: Color yellow);
addMorph: (EllipseMorph new extent: 60@60; color: Color green);
addMorph: (EllipseMorph new extent: 70@70; color: Color blue);
Try also newColumn instead of newRow.
Try using addMorphBack: instead of addMorph.
StringMorph test openInWorld
PluggableButtonMorph example openInWorld
Morphic Event Handling
The first, and most common/best AFAIK, is to use the pattern:
on: eventName send: selector to: recipient
myMorph on: #mouseDown send: #clicked to: self.
A list of recognized eventNames is in EventHandler on:send:to:. I went ahead and copied them.
This is flexible, and allows you to send events to arbitrary objects. You don't have to subclass the Morph. There are also other variations like on:send:to:withValue:. The second way involves working with a Morph subclass. see How to add a mouse-up action in your own subclass of Morph. This method isn't as flexible, and doesn't support as many events, but may be better in some circumstances.
The third way is to create your own EventHandler, and install it with Morph #eventHandler:. I haven't done this myself, but it might be useful if you need to apply many custom behaviors to many different morphs. Morphs can share EventHandlers, btw.
You may want to inspect a MorphicEvent to see exactly what it represents.
Interesting subclasses include:
MouseEvent - #Blue|Red|Yellow ButtonPressed, #position
MouseMoveEvent -#startPoint, #endPoint
These special events have the information you need, like what button was pressed, etc.
Link to this Page