I implemented the even-odd rule to determine if the user clicked on a shape. This only works with polygons, therefore we need to convert curves first (not implemented yet). A path might consist of several polygons (due to "moveTo:"). In that case, we run the algorithm for every polygon ("contour").
This does, however, not work as expected if two shapes intersect each other. In this case, we might want to check if we clicked the upper or the lower shape. The solution is to do the point-in-polygon tests in the inverse rendering order.
Amber-HTML-Morphic contains subclasses of AthensHTMLSurface and AthensHTMLCanvas that provide event handling functionality and some first Morphic ideas (see next post). The idea is to separate the Athens code from event handling and Morphic stuff, such that AthensHTMLSurface and AthensHTMLCanvas only contain code for rendering graphics. Since event handling is platform-specific, we need separate HTML and Cairo implementations.
We can bind events with "onMouseMove: aBlock", ..., just like DOM events in Amber. Step34 in the tutorial shows an example.
"Step 34: Event handling (just a piece of the code)"
surface drawDuring: [:canvas |
path := canvas createPath: [:builder | "Create path"].
poly := path asPolygon].
surface onMouseMove: [:e | surface drawDuring: [:canvas |
surface clear: Color gray.
canvas setShape: path.
(poly includesPoint: e offsetX @ e offsetY)
ifTrue: [canvas setPaint: Color blue]
ifFalse: [canvas setPaint: Color green].
The code above shows a part of the code in step34. "path asPolygon" converts a path to a Polygon. At the moment, this works only for simple paths without curves. A path is a list of operations (e.g. "lineTo:", "moveTo:", "curveVia:", "close", ...) that can be passed to an interpreter.
We have three types of path-related classes in Athens-HTML: "AthensSimplePathBuilder" (part of Athens-Core) generates a path object (that is platform-specific, e.g. "AthensHTMLPath"); "AthensHTMLPath" is the Amber-specific path object that can draw the path by replaying/sending the path commands to itself; "AthensPolygon" is a polygon that can be built by replaying/sending the path commands to an empty instance of it (from "AthensHTMLPath").
In our case, the path is passed to a new "AthensPolygon". This class will also be responsible for flattening curves to polygons. It also provides a method for the point-in-polygon test. If we receive a lot of mouse events, we should cache the polygon when drawing the path (as we did in the example).