3.2 Morph Hierarchy

The Morph class is the root class to all kinds of Morph, sub-classes specialize on specific facets or add some features. Let’s learn a bit more about the Morph class itself and then explore its most fundamental sub-classes.

In a stock Cuis-Smalltalk system, browse the Morph class; it is in the Morphic>Kernel class category, here are the most fundamental type of Morphs:

Observe how the Morph has only PlacedMorph as its subclass, this is odd and it requires some scrunity by looking at the differences between these two classes. Indeed, if Morph has only one sub-class, the two should be merged.

Let’s examine the attributes of the Morph class

Object subclass: #Morph
   instanceVariableNames: 'owner submorphs properties id privateDisplayBounds'
   classVariableNames: 'LastMorphId'
   poolDictionaries: ''
   category: 'Morphic-Kernel'

There are the expected owner and submorphs attributes; the properties attribute is set, when needed, as a dictionary to dynamically add attributes or behaviors – with appropriate bloc of code – to any Morph instances. There are companion methods to set or retrieve a property.

ch04-property

Figure 3.5: Morph’s methods to manipulate properties

This properties attribute is used a lot. For example, observe its use case to set and get a specific name to a Morph instance

name: anObject
"Set the morphName property"
self setProperty: #morphName toValue: 'Glouby'
name
"Answer the value of morphName"
^ self valueOfProperty: #morphName

Create a new morph as see in Example 3.4, then in the inspector execute self name: ’Glouby’, observe how properties was amended with the dictionary entry #morphName->’Glouby’; when invoking the morph’s halo, its new name is printed too.

ch04-morphProperties

Figure 3.6: A morph’s properties

Back to the Morph definition, you can observe there is no attribute to describe the shape of a morph!2 Indeed a morph has no prestablised shape, its shapes is described dynamically – at execution time – through its drawOn: method

drawOn: aCanvas
drawOn: aCanvas
   aCanvas
      fillRectangle: `-75 @ -70 corner: 75 @ 70`
      color: `Color blue`

A Morph instance draw instructions are relative to its owner’s coordinates system. If you observe this blue rectangular morph we created, it is located on the top left corner of the World, where are the (0,0) coordinates; in fact, a large part of this morph is not visible, three quarters of its areas is outside of the screen.

If you observe the halo as see in Figure 3.6, there is no scale, resize and rotate icons! Indeed, a Morph instance can’t be resized or rotated. Moreover, when you try to move the morph through its move icon, you can’t, it sticks to its position, hard coded in its drawOn: method. It is about time to introduce the PlacedMorph class which offers these features.

3.2.1 Morph you can move

...but not only.

These abilities of PlacedMorph to scale, to resize, to rotate and to be moved are some of the differences with Morph.

It is about time to examine its definition

Morph subclass: #PlacedMorph
   instanceVariableNames: 'location layoutSpec'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Morphic-Kernel'

We have two more instance variables:

Time to experiment a bit! Execute this simple code

Sample10PythagorasTree new ::
   openInWorld ;
   inspect

Example 3.5: Inspect a star

to explore with the inspector.

Then invoke its halo to move, resize and rotate to observe how its location attribute is updated:

ch04-location

Figure 3.7: location update according to scale, position and rotation

In one attribute, we have the information on position, size and rotation. Moreover, when sending messages to a morph as #morphPosition, #morphPosition:, #scale:, #rotation:, etc., Cuis-Smalltalk is just querying or adjusting the location matrix parameters. Therefore, you, as the user of a PlacedMorph instance you only use these messages without the need to care about the underlying matrix representation.

One last bit of experiment to foster Morph understanding, Cuis-Smalltalk knows about morph detection; in our previous morph’s inspector, execute and print self coversPixel: 0@0, the answer is likely false if the morph is not positioned to cover the World origin. Now inspect its properties, there is a bitMask entry referring to 1 bit depth Form, this is how Cuis-Smalltalk does pixel detection. In this Form, a kind of picture, each bit represents the pixel obstruction of the morph: 0 means nothing painted, 1 means the morph paints the pixel with an arbitrary color we don’t need to know about here.

This property is automatically garbage collected when not needed anymore or irrelevant.

ch04-morphBitMask

Figure 3.8: Pixel detection illustrated

The reader may wonder if there is any use case of the Morph class when its instances can’t be grabbed and moved around. Why not just using PlacedMorph? In DrGeo software4, such morphs are used to represent geometric objets drawn in the coordinates system of the owner morph, their positions and aspect are completely dependend on the underlying mathematics models.

3.2.2 Rectangular Morph

The remaining classes of the kernel morph hierarchy adds geometry and visual properties. BoxMorph is the most important one, it is bounded to a rectangular extent. It is the root of most sub-morphs used to construct GUI (Graphic User Interface), indeed its elements are most of the time bounded to a rectangular shape. It allows a lot of optimisation in the rendering of the whole Cuis-Smalltalk GUI.

PlacedMorph subclass: #BoxMorph
   instanceVariableNames: 'extent'
   classVariableNames: ''
   poolDictionaries: ''
   category: 'Morphic-Kernel'

This additional extent attribute defines its width (x-axis) and height (y-axis) in the morph local coordinates.

There are companion methods to play with

BoxMorph new ::
   openInWorld;
   morphWidth: 1000;
   morphHeight: 10

Example 3.6: Adjust width and height

Or more directly

BoxMorph new ::
   openInWorld;
   morphExtent: 1000@10

Example 3.7: Adjust extent!

As this is a kind of PlacedMorph, we ask directly about its bounds – position and extent – by executing and printing the result

BoxMorph new ::
   openInWorld;
   localBounds
⇒ 0@0 corner: 50@40.

Example 3.8: Is my place occupying a lot of space?

The returned object is a Rectangle instance, as Point instances, this is a fundamental architecture class when dealing with GUI.

Observe as you get the same local bounds even when positioning around the morph

BoxMorph new ::
   openInWorld;
   morphPosition: 200@200;
   localBounds
⇒ 0@0 corner: 50@40.

Example 3.9: My bounds do not depend on my position

This is because we are asking in the local morph coordinates system. What the reader may want is asking in the owner coordinates system, here the Cuis-Smalltalk World

BoxMorph new ::
  openInWorld;
  fullBoundsInOwner
⇒ 493@594 corner: 543@634

Example 3.10: Morph positioned at pointer position

BoxMorph new ::
   openInWorld;
   morphPosition: 200@200;
   fullBoundsInOwner
⇒ 200@200 corner: 250@240.

Example 3.11: Morph positioned according to programming instruction

There are several methods dealing specifically with BoxMorph, take a look at them and play a bit to foster your understanding. A last bit about this class, earlier at Figure 3.8 we discussed about pixel detection with a complicated morph containing holes. In this situation, the detection is done with an expensive computed mask. BoxMorph optimizes this thanks to its rectangular shape

coversPixel: worldPoint
"Answer true as long as worldPoint is inside our shape even if:
   - a submorph (above us) also covers it
   - a sibling that is above us or one of their submorphs also covers it.
This implementation is cheap, we are a rectangular shape."
^ self coversLocalPoint: (self internalizeFromWorld: worldPoint)

By browsing the implementors of this method, we can read how different it is for a non rectangular morph.

The other morph classes in the kernel category, ColoredBoxMorph and BorderBoxMorph, add cosmetic features as color, border width and color, padding to set space between the border and inner content. It doesn’t require much explanation. PasteUpMorph and WorldMorph are rarely used directly.

This modest dive-in the Morph framework is now over, we hope it provided enough background to foster your understanding when building GUI. In another booklet we will discuss more deeply the Morph framework.


Footnotes

(2)

The other attributes are used for internal managment of the morphs and you don’t need to care about them.

(3)

https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix

(4)

http://gnu.org/s/dr-geo