1.3 Keyboard Event

So far, we have explored how a morph interacts with the mouse pointer. It may also respond to keyboard events. In this section, we modify our EllipseDemo to adjust its color on keyboard interaction.

First, identically to mouse events, we indicate our morph wants to handle keyboard events:

handlesKeyboard
"This enables the morph to handle key events if it has focus."
^  self visible

We handle the keyboard event only when our morph is visible.

Keyboard events are associated with the concept of keyboard focus. In the world of morphs, one or zero morphs own the keyboard focus at a time, meaning this morph will receive the keyboard event.

Moreover, in Cuis-Smalltalk there is a preference #focusFollowsMouse. When true, the keyboard focus is automatically changed to the morph the mouse pointer is hovering over. When false, the keyboard focus is only changed to a morph after a user mouse click on this specific morph.

To know what the preference is in your Cuis-Smalltalk system, execute the code:

Preferences at: #focusFollowsMouse
⇒ true

I personally prefer to explicitly inform the Cuis-Smalltalk system where the keyboard focus should go. Indeed, my mouse tends to slip on my desk, resulting in the keyboard focus changing annoyingly:

Preferences at: #focusFollowsMouse put: false

Our EllipseDemo honors this preference when the mouse pointer enters the morph:

mouseEnter: aMouseEvent
color := `Color green`.
"If the user opted for focus to automatically
move focus to the morph under the cursor then tell
the cursor (event hand) to give focus to this morph."
(Preferences at: #focusFollowsMouse) ifTrue: [aMouseEvent hand newKeyboardFocus: self].
self redrawNeeded

The hand is the mouse pointer object in the Cuis-Smalltalk terminology. It manages the keyboard focus and is informed when the focus should be assigned to another morph.

When the mouse pointer leaves our morph, we let its parent morph manage the focus:

mouseLeave: aMouseEvent
super mouseLeave: aMouseEvent.
color := `Color red`.
self redrawNeeded

according to the #focusFollowsMouse system preference:

Morph>>mouseLeave: evt
(Preferences at: #focusFollowsMouse)
   ifTrue: [evt hand releaseKeyboardFocus: self].
../..

To handle the keyboard strokes, we override the dedicated method keyStroke:. We first ensure the keystroke was not handled by the parent2 then we do the handling specific to our EllipseDemo morph:

keyStroke: aKeyEvent
| character |
super keyStroke: aKeyEvent.
aKeyEvent wasHandled ifTrue: [^ self].
character := Character codePoint: aKeyEvent keyValue.
color := character 
   caseOf: {
      [ $r ] -> [ `Color red` ].
      [ $g ] -> [ `Color green` ].
      [ $b ] -> [ `Color blue` ] }
   otherwise: [color].
self redrawNeeded

The event object is interrogated with dedicated messages to detect modifier keys (i.e., #controlKeyPressed). Browse the UserInputEvent class to discover them all.

To have more flexibility on the color used in our ellipse demo, let’s implement the following features:

keyStroke: aKeyEvent
| character increment h s v |
../..
h := color hue.
s := color saturation.
v := color brightness .
increment := aKeyEvent controlKeyPressed ifTrue: [-0.1] ifFalse: [0.1].
character 
   caseOf: {
      [ $h ] -> [ h := h + (increment * 13) ].
      [ $s ] -> [ s := s + increment ].
      [ $v ] -> [ v := v + increment ] } 
   otherwise: [].
color setHue: h saturation: s brightness: v.
self redrawNeeded

Our gentle introduction ends here. We have exposed several facets of the Morph system to build your own morph from scratch: drawing the morph and handling mouse and keyboard input. In the following chapters, we will explore in more detail the design from scratch of your own morphs and how to combine them with existing morphs.


Footnotes

(2)

For example, to manage keyboard shortcuts or tabulation.