As a first interface to build user interfaces with Athens, I chose morph composition. Every morph is an object that contains behavior and has the ability to draw itself on the Canvas. Every morph has an owner and a list of submorphs.
The class diagram gives an overview of the current Morphic classes. "AthensHTMLMorphicSurface" and "AthensHTMLMorphicCanvas" are the Athens-HTML classes with Morphic and event support. I already presented some functionality, e.g. "onMouseDown:", in the previous post ("Mouse Event Handling").
"AthensHTMLMorphicSurface" has a bidirectional reference to a world morph. The world morph is the overall owner of all submorphs, i.e. every morph will eventually have the world morph as a supermorph (except for the world morph itself). "AthensMorph" provides basic morph functionality, e.g. submorphs handling and drawing. The method "drawOn: canvas" contains Athens code that we usually write inside a "drawDuring:" block. "drawAll" draws the receiver and all of its submorphs (and the entire transitive closure).
Every morph has a position (translation), rotation and a scaling vector. These values are encoded in "transformation" (an "AthensAffineTransform" matrix) and separate instance variables (not shown in the diagram). At first, scaling is applied, then rotation, then translation. "globalTransformation" stores the complete transformation matrix for the morph, including all transformations of owner morphs. Consider, for instance, that A is owner of B. If A is rotated by 90 degrees and B is rotated by 180 degrees, we expect B to be drawn with a rotation of 270 degrees (relative to A's owner). "globalTransformation" is used for mouse events (convert absolute coordinates to morph coordinates) and for drawing. "AthenHTMLMorphicCanvas"'s implicit transformation is set to "globalTransformation". Every time we draw something (inside "drawOn:"), the implicit transformation is applied to the path transformation. I will probably implement this differently, later: it is faster to provide a modified "AthensAffineTransform" as path transform that loads "globalTransformation" with "loadIdentity" and applies "globalTransformation" automatically when setting the matrix values.
"AthensDummyWorldMorph" is just an implementation detail: Morphs might not have an owner directly after creating them while it is still allowed to set transformations at this time. This triggers a redraw automatically and also requires the owner to be redrawn (there might be something behind the morph that now becomes visible). Of course, this does not work if the morph does not yet have an owner and was never drawn yet. "AthensDummyWorldMorph" is a null object.
There are some issues that I could not solve yet: for example, when transforming (e.g. rotating) a morph, I have to redraw the whole world because something behind the morph might become visible. This could involve a lot of rendering, but I cannot think of an easy alternative.
Step35 and step36 of the tutorial contain examples for composing and transforming morphs. You have to run step33 before, because it creates the morphic surface.