|
In Slice, the state tree completely determines the "semantics" of the application. Specifically, this has two parts: (1) how the display appears, and (2) the source node for each event (that is, for each event, the node from which the scripts to be executed are obtained).
The implementation of Slice must respect the semantics given by the tree (which we outline below). We will see that the semantics is fairly straightforward, and that an easy way to do this would be to redraw the tree, from the root, each time the tree changed. But this would be inefficient and would result in an unacceptable amount of flicker. The actual implementation emulates the effect of redrawing the tree, but more efficiently and with less flicker.
This document explains the semantics of the tree, giving a somewhat more formal version of the documentation. It then describes the specific goals of the implementation, the private fields of the state tree (whose purpose is to support efficient redrawing), and the redrawing code itself.
The general outline of how trees are drawn was given in the high-level overview. Here we are more formal, giving pseudo-code to describe how nodes are drawn "from scratch" (which, in fact, is exactly what happens when Slice is started and an initial tree is read in). The implementation is intended to emulate the effect of redrawing from scratch whenever necessary, without actually doing it.
First we discuss drawing and then the determination of source nodes.
This brings us into the main part of the drawing code. Each node is drawn within the coordinate system (bounding box) given by its parent. Note that the boundingbox is in pixel coordinates. If a node has Visible = False, then neither it nor its descendants are drawn.
// Draw visible nodes
Draw (node, scripts, penattrs, boundingbox):
   Add scripts and pen attributes given in node, if any, to scripts and penattrs
   Calculate new boundingbox from boundingbox and X, Y, W, H attributes
     of this node, using default values for any missing attributes
     (0 for X and Y, inherited values for W and H)
   switch(name of node):
     Panel, InkPanel, Button: Allocate object of the given kinds, with
       size given by new bounding box, and associate it with this node.
     Stroke: Strokes always have X, Y, W, and H coordinates, and floats
       field gives stroke points in range [0,1], which are scaled to bounding
       box of the stroke. Use pen attributes either given in the node
       or taken from penattrs; missing attributes are put into the node.
     (All objects created in the preceding switch statement are placed within the
     containing object at the location and size given by the new bounding box.)
   for each child:
     Draw(child, updated scripts, updated penattrs, new bounding box)
The source for each event depends upon the event type, for example:
The source node for each type of event is given in the scripting guide.
The event type and source node together determine the scripts(s) that are executed for an event. From the traversal described above, each node has a list of scripts for each event type (the ones passed into the node plus the ones added by the node, if any). Those are the scripts executed when this is the source node for an event of a given type. They are executed in order of lowest node first, as described in the documentation
The implementation must emulate the effect of drawing the tree using the above algorithm. The basic idea is to respond "minimally" to every change in the tree.
(Terminology: Nodes that are visited during the drawing traversal - i.e. those that appear on the screen - are called visible. Other nodes are called hidden. Note that if a node is hidden, then all its descendants are also hidden. Also, any subtree that is not attached to the state tree is hidden.)
The goals of this code are:
Ease of scripting: Scripters should not have to be concerned about the details of redrawing, but should be concerned only with modifying the tree correctly for their application.
Appearance: Flicker should be minimized.
Efficiency: Aside from the desire for overall efficiency, we have two specific goals:
Redrawing from scratch cannot satisfy the appearance or efficiency goals. Instead, the implementation adjusts the display each time a change is made that might affect it. When such a change is made, the code traverses only that part of the tree that is affected by the change, and does not redraw the entire tree.
To do this, we need to keep track of every change that might affect the display. These changes are:
Note that it is not legal to remove the DisplayId node of a Frame node, since this would leave it (even if temporarily) without a DisplayId value. Similarly, it is not legal to change the Id attribute of a node that is pointed to by a Frame node, because this would again leave the Frame node pointing to nothing (unless there were two nodes with the same Id, which is also illegal).
Tree nodes have private fields which are used to preserve information needed to redraw efficiently. When a node is visible, these fields are known to be correct; when it is hidden, they are not (with some exceptions).
visible: Indicates whether the node is visible.
theObject: The control or stroke objects created by the node, if any.
container: The object into which this node will place any objects it creates; or, for container nodes - Panel, InkPanel, Frame, InkTextBox - the object created by this node.
bbox: Bounding box that represents the coordinate system used by the children of this node. For non-container nodes, this is the size and location, in pixels, of the node. For container nodes, it gives the size of the node, in pixels, but the location is (0,0). (For InkTextBox nodes, it gives the size of the underlying text area.)
locationInParent: For container nodes, the location within the parent (since bbox is set to (0,0)).
penattrs: TreeNode giving the nearest enclosing PenAttributes node (or the PenAttributes node that is a child of this one).
scriptlist: Scripts at this node - inherited node plus scripts defined at this node
When the tree changes in one of the ways described above, a redrawing operation is invoked. These are the operations:
The key questions here are:
Here are the answers we give in this implementation: