View this PageEdit this PageAttachments to this PageHistory of this PageHomeRecent ChangesSearch the SwikiHelp Guide

Morphic Architecture


Morphic architecture

Main Classes

- Morph
- HandMorph (the cursor)
- PasteUpMorph which uses WorldState

Important Global

- World

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.

Help

- You can add Balloon Help to any morph.



PasteUpMorph

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

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.



Morph

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.



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:

RectangleMorph new

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'."
player rewindMovie.
player startPlaying.


Morph Bounds

Every morph has, to a first approximation, a rectangular shape and position. This is summarized by the

#bounds

method, and can be changed with

#bounds:.

#position

gives the point at the top-left corner of a morph, and

#extent

returns a point encoding the width and height of the morph.

There are also several convenience methods such as

#left,
#center,
#height,
#topRight, and so on.



World

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



Balloon Help

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').



Morphic Step

If a morph implements a method

#step,

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

#stepTime

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

#step

is that the model of a SystemWindow can also be stepped. The relevant methods are

#wantsStepsIn:
and

#stepIn:.

The primary use of

#step

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

#step

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.



Morph Structure

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

#addMorph:

or one of its variants such as

#addMorphBack:.

A morph is deleted from its current owner using

#delete.

Finally, owners and submorphs may be queried using

#owner

and

#submorphs

respectively.

To open a new morph initially, use

#openInWorld



SystemWindow

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:
Inspector class>>openAsMorphOn:withLabel:

Another example:
containing a TwoWayScrollPane
containing a PasteUpMorph




AlignmentMorph

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.)

Examples:

AlignmentMorph newRow
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);
position: 20@20;
openInWorld

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

As in:

myMorph on: #mouseDown send: #clicked to: self.

A list of recognized eventNames is in EventHandler on:send:to:. I went ahead and copied them.

o #mouseDown
o #mouseMove
o #mouseStillDown
o #mouseUp
o #mouseEnter
o #mouseLeave
o #mouseEnterDragging
o #mouseLeaveDragging
o #click
o #doubleClick
o #startDrag
o #keyStroke

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.



Morphic Events

You may want to inspect a MorphicEvent to see exactly what it represents.

Interesting subclasses include:
MouseEvent - #Blue|Red|Yellow ButtonPressed, #position
KeyboardEvent -#keyCharacter
MouseMoveEvent -#startPoint, #endPoint


These special events have the information you need, like what button was pressed, etc.





Link to this Page