The Morph class is the root class for all kinds of Morph. Subclasses specialize on specific facets or add features. Let’s learn a bit more about the Morph class itself and then explore its most fundamental subclasses.
In a stock Cuis-Smalltalk system, browse the Morph class; it is in
the Morphic>Kernel
class category. Here are the most
fundamental types of Morphs:
Observe how Morph has only PlacedMorph as its subclass; this is odd and requires some scrutiny by looking at the differences between these two classes. Indeed, if Morph has only one subclass, the two classes 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 blocks of code – to any Morph instance. There are companion methods to set or retrieve a property.
Figure 3.4: Morph’s methods to manipulate properties
This properties attribute is used extensively. For example, observe its use case to set and get a specific name for 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 seen 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 also printed.
Figure 3.5: 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 pre-established shape; its shape is described dynamically through its drawOn: method:
drawOn: aCanvas
drawOn: aCanvas aCanvas fillRectangle: `-75 @ -70 corner: 75 @ 70` color: `Color blue`
A Morph instance’s drawing instructions are relative to its owner’s coordinate system. If you observe this blue rectangular morph we created, it is located at the top left corner of the World, where the (0,0) coordinates are; in fact, a large part of this morph is not visible, three-quarters of its area is outside of the screen.
If you observe the halo as seen in Figure 3.5, there are no scale, resize, and rotate icons! Indeed, a Morph instance cannot 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.
...but not only.
These abilities of PlacedMorph – to scale, resize, rotate, and be moved – are some of the differences compared to 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:
It’s time to experiment! 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:
Figure 3.6: 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 like #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, only use these messages without needing to worry about the underlying matrix representation.
One final experiment to foster an understanding of Morph: 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 a 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 that we don’t need to know about here.
This property is automatically garbage collected when it’s no longer needed or is irrelevant.
Figure 3.7: Pixel detection illustrated
The reader may wonder if there is any use case for the Morph class when its instances cannot be grabbed and moved around. Why not just use PlacedMorph? In DrGeo software4, such morphs are used to represent geometric objects drawn in the coordinate system of the owner morph; their positions and aspects are completely dependent on the underlying mathematical models.
The remaining classes of the kernel morph hierarchy add geometry and visual properties. BoxMorph is the most important one; it is bounded by a rectangular extent. It is the root of most sub-morphs used to construct GUI (Graphic User Interface). Indeed, its elements are most often bounded by a rectangular shape. It allows for significant optimization 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’s 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 with Point instances, this is a fundamental architectural class when dealing with GUI.
Observe that you get the same local bounds even when positioning 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 morph’s local coordinate system. What the reader may want is to ask in the owner’s coordinate 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. One last bit about this class: earlier at Figure 3.7, we discussed 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 like color, border width and border color, and padding to create space between the border and inner content. It doesn’t require much explanation. PasteUpMorph and WorldMorph are rarely used directly.
This brief dive into 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 the Morph framework in more detail.
The other attributes are used for internal management of the morphs and you don’t need to care about them.
https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix