Squeak links to this page:    Squeak Tutorials Tutorial Projects Learning morphic - index BE SimpleButtonMorph Swiki Page List
Morphic Tutorial: Build a 4 digit LED counter
Last updated at 6:25 pm UTC on 11 November 2018
• Probably this tutorial is still fine for Squeak 5.2. -To check.
• The replacement of AlignmentMorph is not necessary as it is still included in Squeak 5.2.

### 2008

updateMe – This is the most complete Morphic tutorial I have found to date. It would be great to update it to 3.9, especially in regard to the use of AlignmentMorphMatthew Fulmer
Revision history
First Draft: Compilation of Squeak list messages by Hannes Hirzel
Third Draft: Hannes Hirzel
Elimination of the instructions from Tim Olson on how to do it by direct manipulation, because Dan Shafter has done a specific Counter Tutorial for this. This tutorial deals with two other ways of creating a simple counter.
Revision of the rest and test with Squeak release versions 2.7, 2.8 and 3.0.
Tested under 3.1 Carlos Crosetti, removing the orientation: messages.

If you like to help please feel free to either add questions, comments or elaborate on the instructions. Anybody who has the knowledge is invited to revise it and come up with either additions on the approaches described here or add additional ones. This tutorial could serve as a starting point for a wider discussion on how to work with Morphic.

## Introduction

A very simple example - let's say a counter - helps to learn Morphic. Using a counter as an example has some tradition in the Smalltalk literature and many books of the last twenty years have an example on this.

There are different approaches to using Morphic. This tutorial elaborates these.

## The problem

Andre M. (Mike) Maloney" asked in comp.lang.smalltalk

> Hi all,

I'm just starting to learn Smalltalk and am using Squeak (2.4c). I'm trying to build a simple counter and need some help. The counter will have three buttons and a display. The displayed value will start at one and the buttons will increment the displayed value by one, decrement the displayed value by one, and reset the displayed value to zero.

I've found the ValueHolder, button, and PluggableButtonController classes, but how do I put the buttons and ValueHolder into a parent frame (window?, view?)?

Squeak has two different interface models: the older MVC (Model, View, Controller), and the newer Morphic. The classes you found are part of MVC. You can use these to build an MVC interface, but it is a little more complicated than Morphic.

This tutorial shows how you can use Morphic to solve this problem.

### Preparation

Make sure you are in a Morphic project. If you are not sure about this look here: Starting up Morphic

### First approach

Morphic has a direct manipulation interface and a programming facility using tiles. You don't need Smalltalk in the classic way for this. Dan Shafer explains the solution using direct manipulation here: Counter Tutorial

### Second approach

Here we will use Smalltalk code to construct the counter. The original description was prepared by Tim Olson.

Daniel McBrearty's comment:
I found Tim's example excellent - if you already know some Smalltalk but want to learn to build morphs from scratch, this is worth exploring. I added comments with a more detailed analysis showing how the LED counter works, step by step. (The only thing I couldn't track was the dependency mechanism - see the query in my code.)

Hannes Hirzel: I reedited the text and added a explanatory note on the 'changed' method.

Tutorial instruction:
Open a new project and then open a new workspace and place it at the right hand side of the screen. Copy all of the following text (until 'third approach') and paste it into the workspace. You can the directly execute the Smalltalk code by selecting it and choosing 'do-it'.

"The object pointed to by the variable frame will be holding the box containing the panel."

frame := AlignmentMorph newColumn."(for version 2.7 and 2.8) or"

frame := AlignmentMorph new listDirection: #topToBottom."(for version 3.0)"

"To make it visible we'll send it the message openInWorld... "

frame openInWorld.
"We now just see a dumb rectangle in the top left corner

We can reposition it by sending it the message position with two coordinate values given by a point:"

frame position: 30 @ 50.

"We now create an LED display for the count"
display := LedMorph new

digits: 4;

extent: (410@150).

" ... to make the display visible we send it as well the message openInWorld. It has all the behaviour of a 4 digit LED display"

display openInWorld.

"it opens on top of frame, but it is draggable ... try it"

"What is it made of? Do print-it on the next line"
display submorphs.

"you should have got something like this (the actual id numbers will be different, every morph get's an id number assigned by the system)

(a LedDigitMorph(2449) a LedDigitMorph(1990) a LedDigitMorph(1560) a LedDigitMorph(2460))"

"Browsing the protocol for LedDigitMorph shows that this is the bit that does the hard work. This class has class variables that define the positions of the segments and the state of each horizontal and vertical segment for each digit and the - sign. It also knows how to change colour and be highlighted or not. Do the following expressions one at a time ... "

(display submorphs at: 2) digit: 5.

(display submorphs at: 2) highlighted: true.

"Note that the second digit was only redrawn when the highlighted message was sent. This is because that method contains the 'self changed' message which causes a redraw.

To test this, modify the 'digit:' instance method. Open a system browser (green window), find the class 'LedDigitMorph' and change the method to look like this ...

digit: anInteger

digit := anInteger \\ 10. ""make sure it stays between 0 and 9""

self changed

.... we have made the individual digit change when its value changes. Test this:"

(display submorphs at: 2) digit: 7.

"A note: The message send 'changed' invokes a method which is implemented in class Morph. It reports that the area should be redrawn. It has nothing to do with the dependency mechanism implemented in class Object which also has a method 'changed'. In Smalltalk-80 this was a heavily used mechanism for building UIs"

"The value of the whole 4-digit array can be changed with the method value::"

display value: 2468.

"Again this uses 'self changed' "

"Now lets add control buttons in a row"

controls := AlignmentMorph newRow. "(for version 2.7 and 2.8) or"

controls := AlignmentMorph new listDirection: #leftToRight."(for version 3.0)"

"how does this look?"

controls openInWorld.

"Drag this away from the previous little rectangle so that the changes will be easy to see.

This is a nice bit. Let's add a quit button"

target: frame;

label: 'quit';

actionSelector: #delete).

"increment button"

target: display;

label: 'inc';

actionSelector: #increment).

target: display;

label: 'dec';

actionSelector: #decrement).

target: display;

label: 'clear';

actionSelector: #clear).

"We have now three morphs we will assemble

• frame (an AlignmentMorph)
• display (a LEDMorph)
• controls (another AlignmentMorph)
"

"add the controls to our frame"

"browsing the protocol for LedMorph again shows a couple of ways to change the appearance, straight away ... "

display color: Color red.

display highlighted: true.

We're not ready to run it yet. We have now built the user interface but the we don't have increment/decrement logic is not yet in place. To make this easy, we'll just add it to
the existing LedMorph class, which we are using for the display.

In the browser, find the LedMorph class, go to the "accessing" category,

and find the "value:" method.
We'll use this method as a template for three additional

methods "increment", "decrement", and "clear". Smalltalkers often use existing
code snippets for creating new code.

• Add a message category 'counting'.
• Add a message increment with the code self value: self value + 1. Accept your change: Hit command-s or select "accept" from the menu in the lower pane.
• Change increment to decrement and change the plus sign in self value: self value + 1 to a minus sign. Choose 'accept'.
• Add a message clear with the code self value: 0

Now we're ready to give it a try.

### Third approach: Separating the UI, the business logic and the application

The following example implementation of the Morphic Counter uses three classes to implement a distinction

Mark Mayfield wrote to the Squeak mailing list on Thu, 19 Aug 1999:

Being someone that is a purest at heart when it comes to separating
business logic, i.e. the model, from its interface, i.e. a view and
controller, here is an alternative to Tim's example. It's a little
more work, but it keeps the LedMorph more of an "interface" object
by making it pluggable. The counter object is a model object, and the counting
application is a cooridnator object in that it creates the counter and pluggable led morph objects and connects them up.
It's not the best code in the world, but it gets the point across.

He also wrote on Monday, 16 Aug 1999
To me a morph is a combination of a view and a controller. I still
create separate model objects that have morphs as dependents.

The three classes

• Counter - the model; responds to the messages currentValue, increment,

decrement and clear.

• PluggableLedMorph - building block for the UI; a new instance is created

and "plugged" on to the model instance in the class method PluggableLedMorph

on: counter value: #currentValue.
This means that the an Instance

of PluggableLedMorph knows that counter is its model an that it

can get it's value by sending it the message currentValue.

• CounterApplication - the controller class for the whole application; does

mainly the assembling and initialisation of the Userinterface display?

in the method openAsMorph. For building the UI AlignmentMorphs,

SimpleButtonMorphs and and a PluggableLedMorph named display are

used. It creates counter - an instance of Counter. The counter

is informed that the display is dependent on it by using counter addDependent:

display.
Whenever the counter executes self changed: #currentValue

the display is informed.

The code:

Select the code, copy it and paste it into a new Workspace. Select it and file it in. Tested with Squeak version 2.7 and 2.8.
For version 3.0 change the instantiation of the AlignmentMorphs in the method openAsMorph in the class 'CounterApplication' as explained under the second approach. (#listDirection: instead of #orientation: ). The code is in the class category 'counting'. You start the application by evaluating
CounterApplication openAsMorph.

Model subclass: #Counter

instanceVariableNames: 'currentValue '

classVariableNames: ''

poolDictionaries: ''

category: 'Counting'!

!Counter methodsFor: 'initialize-release' stamp: 'mlm 8/14/1999

09:30'!

initialize

currentValue _ 0.! !

!Counter methodsFor: 'accessing' stamp: 'mlm 8/14/1999 09:29'!

currentValue

^ currentValue! !

!Counter methodsFor: 'accessing' stamp: 'mlm 8/14/1999 09:29'!

currentValue: anObject

currentValue _ anObject.

self changed: #currentValue! !

!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:31'!

clear

self currentValue: 0! !

!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:32'!

decrement

self currentValue: self currentValue - 1! !

!Counter methodsFor: 'counting' stamp: 'mlm 8/14/1999 09:32'!

increment

self currentValue: self currentValue + 1! !

"– – – – – – – – – – – – – – – – – – "!

Counter class

instanceVariableNames: ''!

!Counter class methodsFor: 'instance creation' stamp: 'mlm 8/14/1999

09:34'!

new

^ super new initialize! !

Model subclass: #CounterApplication

instanceVariableNames: 'counter frame display controls '

classVariableNames: ''

poolDictionaries: ''

category: 'Counting'!

!CounterApplication methodsFor: 'user interface' stamp: 'mlm 8/16/1999

13:25'!

openAsMorph

"create a model object"

counter _ Counter new.

"this is going to be the box containing our panel"

frame := AlignmentMorph new

orientation: #vertical.

"create a 4-digit LED display to contain the count"

display := (PluggableLedMorph on: counter value: #currentValue)

digits: 4;

extent: (410@150).

"make the display a dependent of the model"

"we want our buttons arranged in a horizontal row"

controls := AlignmentMorph new

orientation: #horizontal.

(SimpleButtonMorph new

target: frame;

label: 'quit';

actionSelector: #delete).

(SimpleButtonMorph new

target: counter;

label: 'inc';

actionSelector: #increment).

(SimpleButtonMorph new

target: counter;

label: 'dec';

actionSelector: #decrement).

(SimpleButtonMorph new

target: counter;

label: 'clear';

actionSelector: #clear).

"add the controls and display to our panel"

"start playing with it!!"

frame openInWorld! !

"– – – – – – – – – – – – – – – – – – "!

CounterApplication class

instanceVariableNames: ''!

!CounterApplication class methodsFor: 'instance creation' stamp:

'mlm 8/16/1999 13:25'!

openAsMorph

"CounterApplication openAsMorph"

^self new openAsMorph! !

LedMorph subclass: #PluggableLedMorph

instanceVariableNames: 'getValueSelector model '

classVariableNames: ''

poolDictionaries: ''

category: 'Counting'!

!PluggableLedMorph methodsFor: 'initialize-release' stamp: 'mlm

8/16/1999 13:05'!

on: anObject value: aSelector

self model: anObject.

self getValueSelector: aSelector! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:03'!

getValueSelector

^ getValueSelector! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:05'!

getValueSelector: aSelector

getValueSelector _ aSelector! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:02'!

model

^ model! !

!PluggableLedMorph methodsFor: 'accessing' stamp: 'mlm 8/16/1999

13:05'!

model: aModel

model _ aModel! !

!PluggableLedMorph methodsFor: 'updating' stamp: 'mlm 8/16/1999

13:06'!

update: aSymbol

aSymbol == getValueSelector

ifTrue: [^ self value: (self model perform: self getValueSelector)]! !

"– – – – – – – – – – – – – – – – – –"

PluggableLedMorph class

instanceVariableNames: ''!

!PluggableLedMorph class methodsFor: 'instance creation' stamp:

'mlm 8/16/1999 12:59'!

on: anObject value: aSelector

^ self new on: anObject value: aSelector! !

Source: mailing list archive

topic: "Simple Morphic project".

http://macos.tuwien.ac.at:9009/Server.home

### Other version

Janak on Morphic UI: http://www.cc.gatech.edu/fac/mark.guzdial/squeak/morphicui.html

### Suggestions for further work

• Try to do a counter which counts seconds.
• Go to the Metronome page, download the code there, load it and analyse how it is done.
• Look at the SameGame class to see how a more complex application is assembled.