Hourglass
Volume Number: 5
Issue Number: 5
Column Tag: HindCight
Related Info: Vert. Retrace Mgr
Lisa's Hourglass Is Back! 
By Mike Morton, Waipahu, HI
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
Back to the Future: The Lisa’s hourglass cursor comes to life
In the rivalry between Lisa and Macintosh at Apple, many Lisa features weren’t
carried over into Mac development: a large screen, multi-tasking, stationery pads,
virtual hardware, a huge keyboard Now that these features are slowly reappearing
in Macs, it’s time to bring back one more dead Lisa feature -- the hourglass cursor
-- and also breathe a little life into it. (Let’s just not bring back rectangular pixels.)
This article actually shows you the low-level mechanics of three kinds of
animated cursors:
• a one-handed watch, a simple sequence of ordinary cursors
• a two-handed watch, built by combining cursors
• an hourglass with falling sand: “drawn” by the application
There’s been a lot of discussion (see Further Reading) of whether an animated
cursor should be done by a VBL (timed) task so the main program doesn’t have to keep
calling it. When I originally wrote these routines some years back (in MDS
assembler), I made them a VBL task. I’m no longer sure that’s a good idea. So this
package doesn’t animate on its own, but it should be easy to write a timed task to call
them.
Figure 1. Details of building the hourglass
Figure 2. Components of watch cursor
Some notes on VBL cursor animation
In case you do want to make the cursor run on its own, here are some things to
keep in mind. (Thanks to Seth Lipkin for telling me about a number of these
surprises)
• Remember that when a VBL task wakes up, the globals pointer (A5) isn’t
guaranteed and memory management can’t be performed. Further, under
MultiFinder, the CurrentA5 variable may hold someone else’s A5! You should
store your A5, if you need it, adjacent to your task’s VBL entry.
• There’s a low RAM location called CrsrBusy. As I understand it, when this byte is
non-zero, you shouldn’t do a SetCursor -- I suspect it means there’s a SetCursor
already in progress. If your VBL task sees this flag, it should probably just try
again in one tick.
• The low-memory CrsrVis flag is cleared when the cursor’s not visible (it’s been
hidden or temporarily obscured). Again, call back later.
• Probably the biggest problem with VBL tasks is that they keep running no matter
what. If your application locks up, the user will still see some life, and may not
realize what’s going on. If you must use the VBL trick, you might consider
making the same task function as a “watchdog timer”, trying to figure out if the
application is doing anything. If the application fails to bump some counter at
least every n seconds, the task could hint at this with a different cursor. It’s
very risky to tell the user any other way, since any dialog would mean memory
management. (I’m not sure if a VBL task can use the Notification Manager)
Figure 3. Assembling a two-handed watch
Drawing methods
So if this package isn’t going to run the cursor on its own, what good is it? Well,
it does various kinds of drawing, from simple to tricky. Each of the three cursor types
has a different drawing method.
The watch cursor functions a lot like the Finder’s. There’s a single list of
cursors which are shown in a cycle. This could be done with a Finder-style ACUR
resource, but if you want to do the animation with a timed task, you don’t want to do
GetResource calls. (Actually, you could load the resources and make them
non-purgeable before starting. But another reason is that all the data used by this
watch is also used by the two-handed variant.)
There are 12 frames to this animation sequence, shown in the top part of Figure
2.
The two-handed watch is only slightly more complicated. It uses two sets of data,
one for the minute hand and one (in the bottom of Figure 2) for the hour hand. The
hour hand has only 8 positions; I couldn’t draw 12 hands that small and keep it looking
nice. So don’t worry if it looks like “four-thirty o’clock”.
Figure 3 shows how the hour and minute hand are logically “or”-ed together to
make a two-handed watch. Like a real clock, the hour hand’s position advances only
once per revolution of the minute hand.
The hourglass is a lot more complicated. Looking at parts of Figure 1 shows some
examples of how a grain of sand, a black pixel, moves: At any given time, a single
grain of sand is moving (a). When it lands on top of another grain (b), it randomly
falls to one side or the other (c). If it lands with only one side open (d), it falls to the
open side (e). If it lands with several steps to fall (f), it falls all the way until it
doesn’t have an opening on either side below (g). [Note that the bottom line of the
hourglass must already be solid black pixels, or the sand will try to “leak out”.]
Finally, when a grain of sand drops as far as it can, and it’s above a certain level (h),
the bottom chamber is full, and the cycle starts over.
Every movement by the grain takes a single animation frame. A similar process
is “stealing” a grain from the top chamber. This follows a similar path upwards, but
walks the whole path at once each time it needs a new grain -- see the comments below
on the stealGrain function.
The sample driver program
This main program isn’t a shining example of Macintosh programming style. It’s
just meant to show all the functionality of the subroutines.
Menu commands let you select from among the three types of cursors; control the
speed; and quit. That’s about it.
The structure of the program is simple. The main loop calls the cursor
animation routine at idle time, also calls SystemTask, and then does simple event
handling -- just enough to operate the menus.
One thing to note while using the program is the flicker. On a Macintosh Plus or
SE, the screen refresh is linked to the tick count. Even though the task is not
implemented as a VBL task, the cursor update takes place near the beginning of a tick.
This is apparent on the Plus or SE because the cursor flickers more when it’s near the
top of the screen. On a Mac II, where the monitor’s refresh rate isn’t sync’d with the
ticker, the cursor flickers sporadically at any point on the screen.
A feature I included in the original (ca. 1986) program was a way to measure
how much CPU time the cursor animation takes up. The program would see how many
times it could execute a loop with the cursor running at various speeds, and not
running at all. I haven’t done the measurements for this LightspeedC version, but that
assembler version took up less than 2% of the CPU time if the animation was 15 times
per second or less. Animating at 60 times per second could take up to around 7% of the
time. You have to watch out for this, since your application is presumably showing the
cursor to apologize for some slow operation in the first place!
[A side note: the time used is dependent on the cursor’s horizontal position, since
SetCursor has to shift the image into position -- the function is a sawtooth curve with
a 16-pixel period on a Mac Plus or SE. This took a long time to find]
Finally, note that “main.c” includes “AnimCurs.h”, which contains the
prototypes specifying the interface to the package.
The subroutines
Here’s a quick tour through the contents of the subroutine package.
Data structures: Each of the three cursor variants has a struct defined for it,
remembering the state of the drawing so it can be updated. Then there’s a type
“cursInfo”, which is a union of all three, plus the current cursor, timing
information, and the variable controlling which type of cursor is being shown.
Note that this structure has to remember the whole state of the cursor. For the
watches, it just has to remember one or two hand positions. For the hourglass, it must
keep track of whether or not there’s a falling grain of sand, and the current column
(stored as a bit) and row the sand is at. Unlike with the other types, the cursor data
itself is part of the information, because some routines examine its bits to see where
the sand should fall.
Data:
I’ve included all the masks and data in-line. They probably ought to be
resources. If they were, some care would be needed if the routines were being called
from a VBL task.
Functions:
The three initialization (acStartxxx) routines all do about the same thing: set up
the delay, the type, the cursor mask and data, and type-specific information.
Two utility routines (copy16, or16) are used to copy and combine the “Bits16”
type, which holds a cursor’s mask or data.
hideGrain and showGrain just change the visibility of the current grain of sand.
They’re a good example of how the state information is self-contained; they need no
parameters. moveGrain just updates the position by hiding, moving, and showing the
grain.
coinFlip is kind of cool. I didn’t want to use QuickDraw’s random number
generator. (In addition to needing A5, it can mess up an application’s random sequence
by stealing elements from it nondeterministically.) So I used a method which is
simplified from the engine used in the “DissBits” subroutine. Although the random
numbers used in that subroutine are poor, the random bits used by coinFlip() are
quite random. [See Knuth in Further Reading for details.]
stealGrain takes a random pixel from the top chamber and turns it white. It
works by percolating through black pixels until it reaches the top or runs out of black
pixels. It’s a lot like the way a single black pixel percolates down through white ones,
but it percolates all at once when a new grain is needed. dropGrain just steals a grain
from the top, then sets up a new grain and marks it as moving.
Three routines advance specific cursor types. nextWatch and nextWatch2 are
very simple. nextHour drops a grain if needed and then advances the current grain by
one pixel. doNextFrame just cases out on the current cursor type to call one of these
three.
Two final routines are called from the application: acNext should be called as
often as possible. When it’s time to do so, it computes and displays the next cursor.
acDelay can be called to set the delay, in ticks, between frames.
Projects for a rainy day
I’ve kept the scope of this project small. Here are some varied ways you could
take it from here:
• Intercept GetResource calls for type ACUR and CURS in someone else’s
application. When they think they’re just displaying a sequence of cursors, you
could be computing and displaying something much more complex, such as the
hourglass.
• Sort of the opposite: Intercept SetCursor calls and record the cursors and the
delays in between them. Automatically build and save an ACUR resource. Then
anything drawn “on the fly” (like the hourglass) can be transcribed as an ACUR
and stored for simple programs (like the Finder) to use. Of course, these may
turn out quite large.
• Experiment with changing the mask in the hourglass so the glass part is
“transparent”. You’ll have to modify the mask as the data portion is modified,
too.
• Add an arrow type to the set of cursors supported, so acNext () can always be
called at idle, whether or not there’s animation going on. Hint: if the cursor
variant is an arrow, acNext () does nothing.