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.
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.
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.
...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:
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.
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.
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.
The other attributes are used for internal managment of the morphs and you don’t need to care about them.
https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix