11.5: Interaction and Animation
- Page ID
- 36394
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)To build live user-interfaces using morphs, we need to be able to interact with them using the mouse and the keyboard. Moreover, the morphs need to be able respond to user input by changing their appearance and position — that is, by animating themselves.
Mouse events
When a mouse button is pressed, Morphic sends each morph under the mouse pointer the message handlesMouseDown:
. If a morph answers true
, then Morphic immediately sends it the mouseDown:
message; it also sends the mouseUp:
message when the user releases the mouse button. If all morphs answer false
, then Morphic initiates a drag-and-drop operation. As we will discuss below, the mouseDown:
and mouseUp:
messages are sent with an argument — a MouseEvent
object — that encodes the details of the mouse action.
Let’s extend CrossMorph
to handle mouse events. We start by ensuring that all crossMorphs answer true
to the handlesMouseDown:
message.
\(\bigstar\) Add this method to CrossMorph:
Code \(\PageIndex{1}\) (Squeak): Declaring That CrossMorph Will React to Mouse Clicks
CrossMorph»handlesMouseDown: anEvent ↑true
Suppose that when the red mouse button is clicked, we want to change the color of the cross to red, and when the yellow button is clicked we want to change the color to yellow. This can be accomplished by Code \(\PageIndex{2}\).
Code \(\PageIndex{2}\) (Squeak): Reacting to Mouse Clicks by Changing the Morph’s Color
CrossMorph»mouseDown: anEvent anEvent redButtonPressed ifTrue: [self color: Color red]. anEvent yellowButtonPressed ifTrue: [self color: Color yellow]. self changed
Notice that in addition to changing the color of the morph, this method also sends self changed
. This makes sure that morphic sends drawOn:
in a timely fashion. Note also that once the morph handles mouse events, you can no longer grab it with the mouse and move it. Instead you have to use the halo: blue-click on the morph to make the halo appear and grab either the brown move handle or the black pickup handle at the top of the morph.
The anEvent
argument of mouseDown:
is an instance of MouseEvent
, which is a subclass of MorphicEvent
. MouseEvent
defines the redButtonPressed
and yellowButtonPressed
methods. Browse this class to see what other methods it provides to interrogate the mouse event.
Keyboard events
To catch keyboard events, we need to take three steps.
- Give the “keyboard focus” to a specific morph: for instance we can give focus to our morph when the mouse is over it.
- Handle the keyboard event itself with the
handleKeystroke:
method: this message is sent to the morph that has keyboard focus when the user presses a key. - Release the keyboard focus when the mouse is no longer over our morph.
Let’s extend CrossMorph
so that it reacts to keystrokes. First, we need to arrange to be notified when the mouse is over the morph. This will happen if our morph answers true to the handlesMouseOver:
message.
\(\bigstar\) Declare that CrossMorph will react when it is under the mouse pointer.
Code \(\PageIndex{3}\) (Squeak): We Want to Handle “Mouse Over” Events
CrossMorph»handlesMouseOver: anEvent ↑true
This message is the equivalent of handlesMouseDown:
for the mouse position. When the mouse pointer enters or leaves the morph, the mouseEnter:
and mouseLeave:
messages are sent to it.
\(\bigstar\) Define two methods so that CrossMorph catches and releases the keyboard focus, and a third method to actually handle the keystrokes.
Code \(\PageIndex{4}\) (Squeak): Getting the Keyboard Focus When the Mouse Enters the Morph
CrossMorph»mouseEnter: anEvent anEvent hand newKeyboardFocus: self
Code \(\PageIndex{5}\) (Squeak): Handing Back the Focus When the Pointer Goes Away
CrossMorph»mouseLeave: anEvent anEvent hand newKeyboardFocus: nil
Code \(\PageIndex{6}\) (Squeak): Receiving and Handling Keyboard Events
CrossMorph»handleKeystroke: anEvent | keyValue | keyValue := anEvent keyValue. keyValue = 30 "up arrow" ifTrue: [self position: self position - (0 @ 1)]. keyValue = 31 "down arrow" ifTrue: [self position: self position + (0 @ 1)]. keyValue = 29 "right arrow" ifTrue: [self position: self position + (1 @ 0)]. keyValue = 28 "left arrow" ifTrue: [self position: self position - (1 @ 0)]
We have written this method so that you can move the morph using the arrow keys. Note that when the mouse is no longer over the morph, the handleKeystroke:
message is not sent, so the morph stops responding to keyboard commands. To discover the key values, you can open a Transcript window and add Transcript show: anEvent keyValue
to Code \(\PageIndex{6}\). The anEvent
argument of handleKeystroke:
is an instance of KeyboardEvent
, another subclass of MorphicEvent
. Browse this class to learn more about keyboard events.
Morphic animations
Morphic provides a simple animation system with two main methods: step
is sent to a morph at regular intervals of time, while stepTime
specifies the time in milliseconds between steps.1 In addition, startStepping
turns on the stepping mechanism, while stopStepping
turns it off again; isStepping
can be used to find out whether a morph is currently being stepped.
\(\bigstar\) Make CrossMorph blink by defining these methods as follows:
Code \(\PageIndex{7}\) (Squeak): Defining the Animation Time Interval
CrossMorph»stepTime ↑ 100
Code \(\PageIndex{8}\) (Squeak): Making a Step in the Animation
CrossMorph»step (self color diff: Color black) < 0.1 ifTrue: [self color: Color red] ifFalse: [self color: self color darker]
To start things off, you can open an inspector on a CrossMorph
(using the debug handle in the morphic halo), type self startStepping
in the small workspace pane at the bottom, and do it
. Alternatively, you can modify the handleKeystroke:
method so that you can use the + and − keys to start and stop stepping.
\(\bigstar\) Add the following code to Code \(\PageIndex{6}\).
keyValue = $+ asciiValue ifTrue: [self startStepping]. keyValue = $- asciiValue ifTrue: [self stopStepping].
stepTime
is actually the minimum time between steps. If you ask for astepTime
of 1 ms, don’t be surprised if Squeak is too busy to step your morph that often.