- Model objects are responsible for maintaining the behavior and state of the element.
- View objects are responsible for the representation or appearance of the element within the world.
- Controller objects are responsible for accepting user input and passing messages to the model and view objects.
All interactive elements that exist within the environment in Morphic are subclasses of Morph, and the majority of code that would normally be required by MVC is encapsulated in the Morph class. For example:
Morph new openInWorld.will open a blue rectangle that you can move about the screen, resize, rotate, and more. When a custom morph extends Morph, it inherits all of its parent's functionality and the programmer is freed to focus on customizing the morph instead of writing boiler-plate code. As Maloney wrote in An Introduction to Morphic, "In morphic, a little code goes a long way, and there is hardly any wasted effort."
I actually took advantage of Morphic in my Kember Identity search code, though it's not something one might think of as requiring a manipulatable object. The step message is intended for creating animations and morphs that update themselves dynamically over time (adding liveness to the interface). Morphic repeatedly sends step messages to each morph which may respond by executing their step method (the rate at which step messages are sent to an object can be specified by responding to the stepTime message, but by default I believe it's set for every few milliseconds or so). The method actions run concurrently with whatever else is happening in the rest of the environment. This builtin threading was exactly what I needed to ensure the rest of the system remained responsive to the user while the code was searching for a hash that exhibits the Kember Identity.
I subclassed the TextMorph class and supplied the following step method:
Kember » step found | (currHash = stopHash) ifFalse: [self contents: 'Testing ' , currHash. found := self test: currHash. found ifTrue: [self contents: 'Found ' , currHash , '!']. currHash := self nextHash: currHash. currHash = stopHash ifTrue: [self contents: 'KI was not found.']].The contents: method is inherited from TextMorph to set the displayed text string, and the rest are instance variables and methods specific to my Kember class.
Morphs can respond to several other messages sent by the framework, too. Responding to the drawOn:message allows a morph to customize its appearance, or how it "draws" itself in the environment. Responding to mouseDown:, mouseUp:, mouseMove:, and keyStroke: events lets the morph interact with the user's mouse and keyboard activities.
Well that's 6 posts down now and only 4 more to go before I complete my challenge!