Let’s start the exploration with a few components to ease the creation of a GUI.
Sometimes, labels may tend to occupy more space than is available. This becomes particularly true when you do not control the content of a label, such as when an application is translated into other languages, which may have more or less verbose ways of expressing messages or concepts.
The SqueezeLabelMorph tries its best to contract a message into a given number of characters. It is part of the UI-Core.pck.st package. In a Workspace, install it by executing the appropriate command:
Feature require: 'UI-Core'
This kind of label is set with a minimum number of characters it is willing to display. If that minimum number is lower than the label’s content, it will contract the text:
(SqueezeLabelMorph contents: 'I am a very looong label with maybe not enough place for me' minCharsToShow: 20) openInWorld.
Example 6.1: Label that squeezes
The content of the label is very long, especially as we inform the label to accept being squeezed to a minimum of 20 characters. Observe how such a squeezed label reveals its complete content in a balloon text when the pointer is hovering over it.
Figure 6.1: A label squeezed to 20 characters
When more space is made available to the label, more text of its content is revealed:
Figure 6.2: A squeezed label given some more space
When packing a SqueezeLabel in a layout morph with other morphs, it will have consequences on the minimal width of the owner layout.
Compare the two examples with a squeezed and regular label:
| label row | label := SqueezeLabelMorph contents: 'I am a very long label' minCharsToShow: 15. row := LayoutMorph newRow. row addMorph: label; addMorph: (TextModelMorph withText: 'some input' :: morphExtent: 100@0). row openInWorld
Example 6.2: Squeezed label for a text entry
The whole layout is contracted to a smaller width.
Figure 6.3: A text entry with a squeezed label
When comparing this to a regular label use case:
Figure 6.4: A text entry with a regular label
| row | row := LayoutMorph newRow. row addMorph: (LabelMorph contents: 'I am a very long label'); addMorph: (TextModelMorph withText: 'some input' :: morphExtent: 100@0). row openInWorld
Example 6.3: Regular label for a text entry
It is up to you to decide between the compactness of the GUI and the readability of the labels.
In Text Entry, we presented a quite complex and feature-complete class to handle multiple lines of text editing. When only one line of editing is needed, it is a bit overkill. In that circumstance, you can alternatively use the TextEntryMorph, part of the UI-Entry package:
Feature require: 'UI-Entry'
This class is quite simple, and contrary to the TextModelMorph, it does not need a text model. Therefore, there is no such thing as a changed and update mechanism involved; it is a passive morph.
However, it offers two options to interact with other objects:
Let’s experiment with the associated object answering to the #accept and #cancel messages. We need a TextEntryDemo class:
Object subclass: #TextEntryDemo instanceVariableNames: 'value entry' classVariableNames: '' poolDictionaries: '' category: 'DesignGUI-Booklet'
At initialization time, we create all the needed objects:
initialize
value := '42'. entry := TextEntryMorph contents: value. entry acceptCancelReceiver: self. entry openInWorld
Now we make our TextEntryDemo respond to the #accept and #cancel messages. When pressing Enter, we update our value attribute:
accept
value := entry contents. 'I accepted the input ' print. ('value = ', value) print
But when pressing Esc, we just delete the morph:
cancel
'I discarded the input' print. entry delete
Observe that we only need the accessors #contents/#contents: to set and retrieve its content. It is a very simple class to use. If dependency mechanisms were needed, an intermediate object such as the TextEntryDemo can still be used with the observer pattern.
In example of text entry, we used layout to associate a text entry with a label. It is a very common task when building a GUI. The LabelGroup does exactly that for an arbitrary number of morphs.
Feature require: 'UI-Widgets'
When creating a LabelGroup, we associate labels and widgets/controls into a unique group. In return, the user gets a layout to be inserted in a dialog or a window.
(LabelGroup with: { 'First Name:' -> TextEntryMorph new. 'Last Name:' -> TextEntryMorph new}) openInWorld
Example 6.4: Labelling a group of morphs
Figure 6.5: Text entries associated with labels
The group also gives access to the controls, although it is not a very efficient way to access the input widgets used in the group; it is handy.
Figure 6.6: Access to the controls of a label group
A label group is useful when constructing small dialogs. In the next section, we build one with the morphs we learned in this section and the previous ones.
Small windows the user interacts with are called dialogs or panels. Cuis-Smalltalk-UI offers several alternatives to use.
Feature require: 'UI-Panel'
Let’s rewrite the example of text entry with what we just learned. The end result will look like this:
Figure 6.7: A greeting dialog
In the hierarchy provided by the UI-Panel package, we use the DialogPanel class. It offers both an area to plug our interactive components and an area for our buttons.
DialogPanel subclass: #GreetingPanel instanceVariableNames: 'firstName lastName greetLabel' classVariableNames: '' poolDictionaries: '' category: 'DesignGUI-Booklet'
We set the default color of the dialog:
defaultColor
^ `Color lightOrange`
Then install the iconic buttons for its title:
initialize
super initialize. self showButtonsNamed: #(close expand)
To know about the available buttons for the title bar of a panel, read the class WindowTitleMorph. The expand action needs a rewrite of its associated action:
expandButtonClicked
self fullScreen
We set our components in the dedicated newPane method of the Dialog hierarchy:
newPane
| column group | column := LayoutMorph newColumn :: color: Color transparent; gap: 10 . group := LabelGroup with: { 'First Name: ' -> (firstName := TextEntryMorph contents: '') . 'Last Name: ' -> (lastName := TextEntryMorph contents: '') }. greetLabel := LabelMorph contents: '' font: nil emphasis: 1. column addMorph: group layoutSpec: (LayoutSpec fixedWidth: 300); addMorph: greetLabel layoutSpec: ( LayoutSpec proportionalWidth: 0 fixedHeight: 20 offAxisEdgeWeight: #center). ^ column
The button has its own method too for installation:
newButtonArea
^ PluggableButtonMorph model: self action: #greet label: 'Greet' :: color: self widgetsColor
Finally, we implement the greet action of the button to update the greetLabel:
greet
greetLabel contents: ( 'Hello {1} {2}!' format: {firstName contents. lastName contents})