CHAPTER 4 Structured Graphics

Pick traversal


So far we've examined how the circles and squares are redrawn when manipulated with the mouse and keyboard ("keyboard" here meaning shift and control keys.) Now let's examine how FiggyViewer converts these mouse and keyboard events into the interaction that you see on the display. As described in chapter 2, Fresco's Viewer interface is designed for the handling of input events. Since we want FiggyViewer to handle input events, we inherit from a Viewer implementation called ViewerImpl. This is shown if figure 4-8
above.

When the window receives a mouse event, it wants to find a viewer to handle the event. In order to find the viewer, the window does a pick traversal of the glyph structure to determine if any viewers (or any other glyphs) lie below the mouse. If a viewer does lie below the mouse (we say its been "hit"), then the viewer's handle() operation is called on it.

Below we'll describe how handle() calls FiggyViewer's press(), drag() and release() operations which result in a circle tracking the mouse when you click and drag on it. Before we do, however, let's take a closer look at the mechanics of a pick traversal. As mentioned above, when a glyph structure is traversed, a GlyphTraversal object is passed to each traversed glyph. Figure 4-9 shows part of Fresco's GlyphTraversal interface, and the C++ classes used in a typical implementation.

The GlyphTraversalImpl class defines most of this implementation of the GlyphTraversal interface. As a GlyphTraversalImpl object gets passed down and back up the structure, it maintains a growing and shrinking list (actually, a stack) of information representing the current state of the traversal. This list represents a "trail" or "path" to the currently traversed glyph. The PickTraversal object is a subclass of GlyphTraversalImpl which allows us to grab a "snapshot" of this trail at a given "hit" object. The PickTraversal does this by providing an implementation of the hit() operation (which GlyphTraversalImpl defines as a nop.) When hit() is called on a PickTraversal, it creates a new PickTraversal object (a copy of itself) which acts as a snapshot of the trail to the picked glyph. This new PickTraversal object is placed as the head of a chain of hit objects. Figure 4-10 shows a PickTraversal object being passed from glyph to glyph in a structure where hit() is called on the traversal as it reaches glyph ....

Note that there is more than one possible trail to glyph ... in the structure. Why is getting a snapshot of a particular trail important? This is because in order to manipulate a hit glyph (such as the mouse tracking you see when clicking and dragging a circle) we need to obtain the cumulative transformation state to that glyph. This state must be accumulated along the particular trail that was hit. We'll look more at transformations below.

In figure 4-10 the traversal object stack shows glyphs at each level. Actually more information is stored at each level. Figure 4-11 below shows the information maintained by the GlyphTraversalImpl at each level of the stack. Some entries in the stack denote edges/glyphs in the structure and some entries denote "trail heads".

Let's examine what would happen during a pick traversal if glyph ... of figure 4-10 happened to by our FiggyViewer. In this case, the FiggyViewer wants to report that it has been hit so that handle() will be called on it. FiggyViewer inherits its traverse() behavior from ViewerImpl. The implementation is shown in figure 4-12:

Here's an explanation of what's happening. First, a PickTraversal comes in (line 1) so the expression t->op() returns one of the three "pick" values (lines 4-6).

The variable visit_child is set true because the "allocation" (screen real estate) given the FiggyViewer during traversal does indeed intersect the mouse position. The variable visit_child is later used as a cull test in line 15.

The variable hit is set to true because our FiggyViewer is opaque, or non-transparent. We had set the FiggyViewer to be opaque in its initialization statement.

FiggyViewer::FiggyViewer(
	Fresco* f, Coord w, Coord h
) : ViewerImpl(f, false /* opaque */) {
Opaque here means that this viewer should register itself as "hit" regardless of whether any contained glyphs lie below the mouse cursor. In the case of dragging a circle, the circle itself will register the hit during the call to traverse_child in line 17, and this in itself is enough to trigger the window to call handle() on the FiggyViewer. We've defined FiggyViewer to be opaque because we need hit() in line 19 to be called when the mouse is over "white space". This is what triggers the call to handle() that allows us to create new circles and squares, or, if the shift or control keys are down, to scroll or zoom the entire drawing.


Copyright (c) 1994 by Steve Churchill
Comments or questions? Contact Steve Churchill (stevec@faslab.com)

Generated with CERN WebMaker