TweetFollow Us on Twitter

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 or source 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.

• Handle color cursors.

• Time the routines and choose an optimal animation rate to keep the animation smooth yet lessen flicker and not use too much CPU time. Try it on various CPUs and monitors to check both performance and appearance.

• Write a VBL task which makes the cursor animate automatically. Have it notice when another part of the program does a SetCursor trap, and assume this means it should turn itself off. (In an older program, I examined the low-memory cursor data to see if the cursor had changed; probably a bad move for color cursors )

• Put everything into a driver, so any development system can use it. Could you have it get time during SystemTask calls, so the application needn’t call it?

Further reading

For more on the method used in the coinFlip() routine, see:

• Donald Knuth, The Art of Computer Programming, 2nd ed., Vol II: Seminumerical Algorithms,pp. 29-31

• Mike Morton, “A Digital Dissolve Effect for Bitmapped Graphics Screens”, Dr. Dobb’s Journal , November 1986

• Mike Morton, “The DissBits Subroutine”, MacTutor, December 1985

• William H. Press, et al, Numerical Recipes: The Art of Scientific Computation, pp. 209-213

Knuth’s writeup is the most interesting as far as a theoretical basis. The Recipes includes a useful table of constants. My DDJ article is more in-depth as far as applying the method.

For more on the vertical retrace manager, see:

• “The Vertical Retrace Manager”, Inside Macintosh, Vol. II, pp. 347-354

• Robert B. Denny, “Using the Vertical Retrace Manager”, August 1985

For more on animation (of cursors or anything), see:

• Dick Chandler, “VBL Task Animation”, MacTutor, February 1989

• Stuart Gordon, dir., “Re-Animator”, 1985

• Letters, MacTutor, March 1988, p. 12

• Mike Morton, “Alternate Video Screen Animation”, MacTutor, June 1986

• David Stoops, “Animated Cursors”, Letters, MacTutor, December 1988, pp. 108-109

Conclusions

We’ve covered three ways to animate cursors -- two simple ones based on resource-like data and one which does the drawing more “by hand”. Doing the drawing directly into a cursor with bit manipulation is much faster than using QuickDraw to set and clear pixels. It’s acceptable because the format of a monochrome cursor isn’t about to change.

The animation routines are decoupled from the issue of whether the animation should be invoked from an idle routine or from a timed task. Timed tasks bring the risk that a user gets no indication of when the application is hung up, along with various technical subtleties. If you do want to make a task animate your cursor, it might be a good idea to make the task also check for signs of life in the application.

Listing:  AnimCurs.h

/* Prototypes for the animated cursor subroutines.
 See “AnimCurs.c” for details on how to call these.
*/

void acStartHG (int delay);/* set up for hourglass cursor */
void acStartW1 (int delay);/* set up for one-handed watch */
void acStartW2 (int delay);/* set up for two-handed watch */

void acDelay (int delay); /* set delay between frames */

void acNext (void);/* idle-time call: advance cursor */
Listing:  AnimCurs.c

/* Animated Cursor subroutines.

Copyright © 1989 by Michael S. Morton, all rights reserved.
Written February ’89 for MacTutor.  Feel free to use these or
modify them so long as the original author and source are noted.

 Routines included are:
 • acStartHG -- set the cursor to be an hourglass
 • acStartW1, acStartW2 -- set the cursor to be a 1- or 2-handed watch
 • acDelay -- set the delay, in ticks, between animation steps
 • acNext -- idle-time routine 

To set up any of these cursors, call the acStart___ routine, passing 
the delay you’d like between frames.  Then call acNext during your idle 
routine.  To change the delay, call acDelay.  To get a different kind 
of cursor, stop calling acNext and use SetCursor to get it.
*/

/* State information for three cursor types: */
typedef struct   /* info on the HOURGLASS: */
{long coin; /* used in random number generator */
 int grainRow;   /* row number (0 15) of falling grain */
 int grainBit;   /* bit mask for falling grain */
 Boolean grainMoving;/* flag: is the grain still moving? */
} hourInfo; /* define this type */

typedef struct   /* info on ONE-HANDED WATCH: */
{int wFrame;/* which frame of animation are we on? */
} watch1Info;    /* define this type */

typedef struct   /* info on TWO-HANDED WATCH: */
{int mFrame;/* frame of minute hand */
 int hFrame;/* frame of hour hand */
} watch2Info;    /* define this type */

/* Generalized cursor type: */
typedef enum
{ hgType, w1Type, w2Type } cursType; /* selects one of above structs 
*/

typedef struct   /* tracks any kind of cursor */
{Cursor curs;    /* current cursor */
 int delay; /* delay between frames (ticks) */
 long nextFrame; /* time of next frame (ticks) */
 cursType type;  /* type of cursor */
 union  /* depending on type of cursor: */
 { hourInfo hg;  /*  • info on hourglass */
 watch1Info w1;  /*  • info on one-handed watch */
 watch2Info w2;  /*  • info on two-handed watch */
 } u;   /* give union an easy name */
} cursInfo, *ciPtr;

/* cursor information is used globally all through file. */
cursInfo theInfo;/* sole instance of this struct */

/* 16x16 data and masks for cursors.  Some of these are OR’d together 
to make the two-handed watch.  Others are just backgrounds which are 
drawn on, such as for the hourglass. */
int hourData [16] =/* hourglass, about to start falling */
 { 0xFFFE, 0x7FFC, 0x7FFC, 0x7FFC, 0x5FF4, 0x4FE4, 0x47C4, 0x4384, 0x4284, 
0x46C4, 0x4C64, 0x5834, 0x701C, 0x600C, 0x4004, 0xFFFE };
int hourMask [16] = { 0xFFFE, 0x7FFC, 0x7FFC, 0x7FFC, 0x5FF4, 0x4FE4, 
0x47C4, 0x4384, 0x4384, 0x47C4, 0x4FE4, 0x5FF4, 0x7FFC, 0x7FFC, 0x7FFC, 
0xFFFE };

int watchMask [16] = {  0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x7F00, 0xFF80, 
0xFF80, 0xFFC0, 0xFFC0, 0xFF80, 0x7F00, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 
0x0000 };

int watchMinutes [12][16] = /* twelve minute-hand positions */
{
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4900, 0x8880, 0x8880, 0x88C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4300, 0x8480, 0x8480, 0x88C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8180, 0x8680, 0x88C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x8FC0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x88C0, 0x86C0, 
0x8180, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x88C0, 0x84C0, 
0x8480, 0x4300, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x88C0, 0x88C0, 
0x8880, 0x4900, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x88C0, 0x90C0, 
0x9080, 0x6100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x88C0, 0xB0C0, 
0xC080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0xF8C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0xC080, 0xB080, 0x88C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x6100, 0x9080, 0x9080, 0x88C0, 0x80C0, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 }
};

int watchHours [8][16] =  /* eight hour-hand positions */
{
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8880, 0x8880, 0x8880,0x8080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8480, 0x8880,0x8080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x8E80,0x8080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x8880, 0x8480, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x8880, 0x8880, 
0x8880, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0x8880, 0x9080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x8080, 0xB880, 0x8080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 },
 { 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x4100, 0x8080, 0x9080, 0x8880, 0x8080, 
0x8080, 0x4100, 0x3E00, 0x3E00, 0x3E00, 0x3E00, 0x0000 }
};

/* Function prototypes for stuff we define externally: */
#include “AnimCurs.h”

/* Function prototypes for our static stuff: */
void acStartW (cursType type, int delay); /* start either kind of watch 
*/
void copy16 (int *src, Bits16 *dst);/* copy 16x16 bit image */
void or16 (int *src, Bits16 *dst); /* OR a 16x16 bit image */
void hideGrain (void);    /* hide current grain in hourglass */
void showGrain (void);    /* show current grain in hourglass */
Boolean coinFlip (void);  /* produce a pseudo-random boolean */
void stealGrain (void); /* remove a grain from top section */
void dropGrain (void);    /* get a grain moving */
void moveGrain (int dh, int dv);/* move a grain in transit */
void nextHour (void);/* compute next cursor: hourglass */
void nextWatch (void);    /* watch */
void nextWatch2 (void); /* 2-handed watch */
void doNextFrame (void);  /* compute next frame and show it */

/* acStartHG () -- Initialize the hourglass cursor, with the specified 
delay between animation frames.  This is an external routine. */
void acStartHG (delay)
 int delay; /* INPUT: pause between frames */
{theInfo.delay = delay; /* record delay in structure */
 theInfo.nextFrame = TickCount () + delay; /* figure when to animate 
next */
 theInfo.type = hgType; /* remember the type: hourglass */
 theInfo.u.hg.grainRow = -1; /* make a grain drop in dropGrain () */

 /*Initialize cursor data, mask and hotspot (altho dropGrain does data, 
too) */
 copy16 (hourData, & (theInfo.curs.data));   
 copy16 (hourMask, & (theInfo.curs.mask));
 theInfo.curs.hotSpot.v = 8;/* stick hotspot  */
 theInfo.curs.hotSpot.h = 8;/*  in middle */

 dropGrain ();   /* start first grain dropping */
}/* end of acStartHG () */

/* acStartW1 () -- Just like acStartHG, but with a one-handed watch. 
This is an external routine.
*/
void acStartW1 (delay)
 int delay; /* INPUT: pause between frames */
{acStartW (w1Type, delay);
}/* end of acStartW1 () */

/* acStartW2 () -- Just like acStartHG, but with a two-handed watch. 
This is an external routine.
*/
void acStartW2 (delay)
 int delay; /* INPUT: pause between frames */
{acStartW (w2Type, delay);
}/* end of acStartW2 () */

/* acStartW () -- Start a one- or two-handed watch. */
static void acStartW (type, delay)
 cursType type;  /* INPUT: w1Type or w2Type */
 int delay; /* INPUT: pause between frames */
{theInfo.delay = delay;   /* record delay in structure */
 theInfo.nextFrame = TickCount () + delay; /* figure when to animate 
next */
 theInfo.type = type;/* remember the type */

 if (type == w1Type) /* init different types: */
 theInfo.u.w1.wFrame = 0; /* one-handed: set to zero */
 else
 { theInfo.u.w2.mFrame = 0; /* two-handed: set minute */
 theInfo.u.w2.hFrame = 0; } /*  and hour frame to zero */

 /*Initialize the cursor data, mask and hotspot: */
 copy16 (watchMinutes [0], & (theInfo.curs.data));
 copy16 (watchMask, & (theInfo.curs.mask));
 theInfo.curs.hotSpot.v = 8;/* stick hotspot  */
 theInfo.curs.hotSpot.h = 8;/*  in middle */
}/* end of acStartW () */

/* copy16 () -- Copy an array of 16 ints into a Bits16 structure (the 
data or mask of a cursor. */
void copy16 (src, bits)
 register int *src;/* INPUT: source data */
 Bits16 *bits;   /* OUTPUT: where to stuff it */
{register int *dst = (int *) bits; /* coerce to a handier data type */
 int i; /* FORTRAN-ish loop counter */

 for (i = 0; i <= 15; i++) *dst++ = *src++; /* copy 16 words */
}/* end of copy16 () */

/* or16 () -- Just like copy16(), but we OR, not COPY. */
void or16 (src, bits)
 register int *src;/* INPUT: source data */
 Bits16 *bits;   /* OUTPUT: where to stuff it */
{register int *dst = (int *) bits; /* coerce to a handier data type */
 int i; /* FORTRAN-ish loop counter */

 for (i = 0; i <= 15; i++) *dst++ |= *src++; /* OR 16words */
}/* end of or16 () */

/* hideGrain () -- Hide pixel where the current grain is. */
static void hideGrain ()
{theInfo.curs.data [theInfo.u.hg.grainRow] &=
 (~ theInfo.u.hg.grainBit);
}

/* showGrain () -- Show the pixel where current grain is. */
static void showGrain ()
{theInfo.curs.data [theInfo.u.hg.grainRow] |=
 theInfo.u.hg.grainBit;
}

/* coinFlip () -- Return a pseudo-random boolean value.  We use shift-register 
system detailed in Knuth, among other places. */
static Boolean coinFlip ()
{register Boolean result;

 if (! theInfo.u.hg.coin) /* zero? */
 theInfo.u.hg.coin = 1;   /* avoid demon state */
 result = (theInfo.u.hg.coin < 0);/* note if high bit set */

 theInfo.u.hg.coin <<= 1; /* shift old value over 1 bit  */
 if (result)/*  and if top bit was ‘1’  */
 theInfo.u.hg.coin ^= 0xc5; /*  then futz with low bits */

 return (result);
}/* end of coinFlip () */

/* stealGrain -- Grab a grain of sand from the top chamber of the hourglass. 
We want to percolate a white pixel up from the starting bit position. 
*/
static void stealGrain ()
{register int v = 6; /* start at row 6, tip of top half */
 register int hBit = 0x0100;/* start at the middle */
 register Boolean done = false;  /* haven’t finished percolating yet 
*/
 register int aboveWord;  /* row of sand grains above us */
 register Boolean leftOK, rightOK; /* are there bits to steal above? 
*/

 while (! done)
 { --v; /* assume we can move up a row */
 aboveWord = theInfo.curs.data [v];/* get that row from the cursor image 
*/

 /*Decide which direction to steal from -- if there’s a grain right above 
us, we’ll take that.  Otherwise, we have to choose between the ones diagonally 
up from us. */
 if ((aboveWord & hBit) == 0) /* have a bit above us? */
 { leftOK = (0 != (aboveWord & (hBit << 1))); /* have someone above on 
left? */
 rightOK = (0 != (aboveWord & (hBit >> 1))); /* someone above on right? 
*/

 /*Branch, depending on which way(s) we can go: */
 if (leftOK && rightOK)   /* both available? */
 { if (coinFlip ()) hBit <<= 1;  /* yes: flip a coin  */
 else hBit >>= 1;/*  to decide */
 }
 else if (leftOK) hBit <<= 1; /* just left: take it */
 else if (rightOK) hBit >>= 1;/* just right: take it */
 else { ++v; done = true; } /* neither: back down */
 } /* end of no bit above us */

 if (v <= 1) done = true; /* if we hit top row, we have to stop */
 } /* end of while-not-done */

 theInfo.curs.data [v] &= (~ hBit);/* snuff this bit */
}/* end of stealGrain () */

/* dropGrain () -- Get next grain of sand to fall.  The “moving” flag 
is false. We get one grain of sand from the top chamber.  If the bottom 
chamber is filled, we reinit the cursor to a full top chamber.  Either 
way, we flagthat a grain is moving, and draw it.
*/
static void dropGrain ()
{stealGrain ();  /* pull a grain from the top chamber */

 /*If the pile in the bottom chamber has filled it, the most recent grain 
dropped didn’t get very far. */
 if (theInfo.u.hg.grainRow < 9)  /* are we piled too high? */
 copy16 (hourData, & (theInfo.curs.data));   /* yes: redraw all */

 theInfo.u.hg.grainRow = 8; /* set row number of new grain */
 theInfo.u.hg.grainBit = 0x0100;   /* set bit in row */
 theInfo.u.hg.grainMoving = true;  /* we’re cooking with gas now */
 showGrain ();   /* show the grain */
}/* end of dropGrain () */

/* moveGrain () -- Move the current grain by a given delta.  Note that 
a positive “dh” is a right shift. */
static void moveGrain (dh, dv)
 int dh, dv;/* INPUT: amount to move by */
{hideGrain ();   /* hide where it is now */

 theInfo.u.hg.grainRow += dv; /* update vertical position */
 if (dh > 0)/* update horizontal position */
 theInfo.u.hg.grainBit >>= dh;
 else theInfo.u.hg.grainBit <<= -dh;

 showGrain ();   /* show where it is now */
}/* end of moveGrain () */

/* nextHour () -- Advance the hourglass by one frame. */
static void nextHour ()
{register int v, hBit;/* coordinate and bit pos of grain */
 register int nextWord;   /* word of grains below us */
 register Boolean leftOK, rightOK; /* flags for OK directions to fall 
*/

 if (! theInfo.u.hg.grainMoving)   /* grain hit bottom last time? */
 dropGrain ();   /* yes: get a new one */

 v = theInfo.u.hg.grainRow; /* grab position and bit just  */
 hBit = theInfo.u.hg.grainBit;/*  for easier typing & efficiency */

 nextWord = theInfo.curs.data [v+1]; /* get word below this one */
 if ((nextWord & hBit) == 0)/* slot below us free? */
 { moveGrain (0, 1); /* yes: just move down one */
 return;/* and that’s all we need */
 }

 leftOK = (0 == (nextWord & (hBit << 1))); /* have nobody below on left? 
*/
 rightOK = (0 == (nextWord & (hBit >> 1))); /* nobody below on right? 
*/

 /*Branch, depending on which way(s) we can go: */
 if (leftOK && rightOK)   /* can fall left or right? */
 { if (coinFlip ())/* yes: randomly choose: */
 moveGrain (1, 1); /*  • fall to the right */
 else moveGrain (-1, 1);  /*  • fall to the left */
 }
 else if (leftOK) moveGrain (-1, 1); /* left only: do it */
 else if (rightOK) moveGrain (1, 1); /* right only: do it */
 else theInfo.u.hg.grainMoving = false; /* can’t move: stop; reset next 
time */
}/* end of nextHour () */

/* nextWatch -- Compute the next frame of one-handed watch animation. 
*/
static void nextWatch ()
{++ theInfo.u.w1.wFrame;  /* bump frame ahead by 1 */
 if ((theInfo.u.w1.wFrame) >= 12)/* outside of range 0 11? */
 theInfo.u.w1.wFrame = 0; /* yes: wrap around */

 copy16 (watchMinutes [theInfo.u.w1.wFrame],
 & (theInfo.curs.data));
}/* end of nextWatch () */

/* nextWatch2 -- Compute the next frame of two-handed watch animation. 
*/
static void nextWatch2 ()
{++ theInfo.u.w2.mFrame;  /* bump MINUTE frame ahead by 1 */
 if ((theInfo.u.w2.mFrame) >= 12)/* outside of range 0 11? */
 theInfo.u.w2.mFrame = 0; /* yes: wrap around */

 copy16 (watchMinutes [theInfo.u.w2.mFrame],
 & (theInfo.curs.data));

 if (theInfo.u.w2.mFrame == 0)/* MINUTE frame just hit 12 o’clock? */
 { ++ theInfo.u.w2.hFrame;/* yes: bump HOUR frame ahead by 1 */
 if ((theInfo.u.w2.hFrame) >= 8) /* outside of range 0 7? */
 theInfo.u.w2.hFrame = 0; /* yes: wrap around */
 }

 /*Add hour-hand image into minute-hand. */
 or16 (watchHours [theInfo.u.w2.hFrame],
 & (theInfo.curs.data));
}/* end of nextWatch () */

/* doNextFrame -- Compute next frame, dispatching on type of cursor. 
*/
static void doNextFrame ()
{switch (theInfo.type)
 { case hgType:  nextHour (); break;
 case w1Type:  nextWatch ();break;
 case w2Type:  nextWatch2 (); break;
 default: break;
 } /* end of switch on cursor type */
}/* end of doNextFrame () */

/* acNext-Idle-time animation.  This is an external routine.*/
void acNext ()
{if (TickCount () < theInfo.nextFrame) /* time to animate yet? */
 return;/* nope: return */

 theInfo.nextFrame = TickCount () + theInfo.delay; /* reset wake-up call 
*/
 doNextFrame (); /* draw next frame internally */
 SetCursor (& (theInfo.curs));/* and display it */
}/* end of acNext () */

/* acDelay -- Set delay, in ticks, between frames.  This is an external 
routine.
*/
void acDelay (delay)
 int delay; /* INPUT: new delay */
{theInfo.delay = delay;
 theInfo.nextFrame = TickCount () + delay; /* figure when to animate 
next */
}/* end of acDelay () */
Listing: main.c

/* Animated Cursor demonstration main program.

Copyright © 1989 by Michael S. Morton, all rights reserved.
 Written February ’89 for MacTutor.
*/

#include “AnimCurs.h”/* define functions in package */

/* Internal prototypes: */
void main (void);
void makeMenus (void);
void doMenu (long result);

int theDelay = 4;/* delay between animation frames, tix */

enum    /* items in our sole menu */
{mnWatch = 1, mnWatch2, mnHourG, mnUnused1,
 mnFaster, mnSlower, mnTop, mnUnused2, mnQuit };

void main ()
{EventRecord evtRec; /* info from GetNextEvent () */
 int inWhat;/* from FindWindow (): where was click? */
 WindowPtrtheWin;/* unused -- just for FindWindow () */
 long menuResult;/* from MenuKey () or MenuSelect () */

 /*Usual initializations: perhaps more than we need here. */
 InitGraf (&thePort);/* initialize QuickDraw */
 InitFonts ();   /* init the Font Manager */
 FlushEvents (everyEvent, 0);/* ignore pending events */
 InitWindows (); /* init the Window Manager */
 InitMenus ();   /* init the Menu Manager */
 TEInit (); /* init TextEdit */
 InitDialogs (0L); /* init Dialog Mgr; no restart proc */
 InitCursor ();  /* show standard arrow cursor */

 makeMenus ();   /* set up one cheap little menu */
 acStartW1 (theDelay);  /* initialize to a one-handed watch */

 /*Main loop: at idle time, update the cursor.  Check for menu commands 
via the mouse or keyboard. */
 while (1)
 { acNext ();    /* idle time: animate the cursor */
 SystemTask ();  /* can’t hurt, altho we don’t do DAs */

 if (GetNextEvent (everyEvent, & evtRec))    /* try for an event */
 { if (evtRec.what == mouseDown)   /* click? */
 { inWhat = FindWindow (evtRec.where, &theWin); /* look up where it hit 
*/
 if (inWhat == inMenuBar) /* menu click? */
 doMenu (MenuSelect(& (evtRec.where))); /* track & process menu hit */
 else SysBeep (2); /* can’t handle clicks elsewhere */
 } /* end of handling mouseDown */

 else if (evtRec.what = keyDown)   /* keypress?  */
 doMenu (MenuKey((char) evtRec.message)); /* don’t require cmdKey */
 } /* end of handling one event */

 } /* end of infinite loop */
}/* end of main () */

/* Make menu in-line.  (Resources?  This is just a demo driver ) */
void makeMenus ()
{MenuHandle theMenu; /* our sole menu */

 theMenu = NewMenu (1, “\pMenu”);
 AppendMenu (theMenu, “\pWatch/W;Two-handed watch/2;Hourglass/H;(-”);
 AppendMenu (theMenu, “\pFaster/F;Slower/S;Top speed/T;(-”);
 AppendMenu (theMenu, “\pQuit/Q;(-”);
 AppendMenu (theMenu, “\pAnimated Cursor Demo;written in LightspeedC”);
 AppendMenu (theMenu, “\pCopyright © 1989;Michael S. Morton;for MacTutor”);
 InsertMenu (theMenu, 0); /* put it in the menu bar */
 DrawMenuBar (); /* show our handiwork */
}/* end of makeMenus () */

void doMenu (result)
 long result;    /* INPUT: result from MenuSelect/Key */
{int command = LoWord (result);  /* extract item number */

 switch (command)
 { case mnWatch: acStartW1 (theDelay); break;
 case mnWatch2:  acStartW2 (theDelay); break;
 case mnHourG:   acStartHG (theDelay); break;

 /*Note that we can’t let the delay go down to zero for “Faster”. */
 case mnFaster:  if (theDelay>1) --theDelay; acDelay (theDelay); break;
 case mnSlower:  ++theDelay;  acDelay (theDelay); break;
 case mnTop:theDelay = 1; acDelay (theDelay); break;

 case mnQuit:    ExitToShell (); break;
 default: break; /* ignore unrecognized items */
 } /* end of switch on command */

 HiliteMenu (0);
}/* end of doMenu () */

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Spotify 1.0.53.758. - Stream music, crea...
Spotify is a streaming music service that gives you on-demand access to millions of songs. Whether you like driving rock, silky R&B, or grandiose classical music, Spotify's massive catalogue puts... Read more
beaTunes 4.6.14 - Organize your music co...
beaTunes is a full-featured music player and organizational tool for music collections. How well organized is your music library? Are your artists always spelled the same way? Any R.E.M. vs REM?... Read more
Vienna 3.1.10 :d05d7a5d: - RSS and Atom...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
Day One 2.1.8 - Maintain a daily journal...
Day One is the easiest and best-looking way to use a journal / diary / text-logging application for the Mac. Day One is well designed and extremely focused to encourage you to write more through... Read more
iDefrag 5.1.8 - Disk defragmentation and...
iDefrag helps defragment and optimize your disk for improved performance. Features include: Supports HFS and HFS+ (Mac OS Extended). Supports case sensitive and journaled filesystems. Supports... Read more
Arq 5.7.9 - Online backup to Google Driv...
Arq is super-easy online backup for Mac and Windows computers. Back up to your own cloud account (Amazon Cloud Drive, Google Drive, Dropbox, OneDrive, Google Cloud Storage, any S3-compatible server... Read more
VirtualBox 5.1.20 - x86 virtualization s...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
beaTunes 4.6.14 - Organize your music co...
beaTunes is a full-featured music player and organizational tool for music collections. How well organized is your music library? Are your artists always spelled the same way? Any R.E.M. vs REM?... Read more
iDefrag 5.1.8 - Disk defragmentation and...
iDefrag helps defragment and optimize your disk for improved performance. Features include: Supports HFS and HFS+ (Mac OS Extended). Supports case sensitive and journaled filesystems. Supports... Read more
Day One 2.1.8 - Maintain a daily journal...
Day One is the easiest and best-looking way to use a journal / diary / text-logging application for the Mac. Day One is well designed and extremely focused to encourage you to write more through... Read more

Latest Forum Discussions

See All

Blizzard is looking to hire a mobile dev...
A new thread on the popular video game rumor forum, NeoGAF, uncovered an interesting job listing over at Blizzard Entertainment. It appears the studio behindStarCraft, World of WarCraft, Hearthstone,andOverwatch is looking to bring on a new hire... | Read more »
Legend of Zelda meets Cooking Mama in ne...
Dungeon Chef is what happens when you mix the RPG elements (and style) of a Legend of Zelda game, with cooking elements. Although, now that The Legend of Zelda: Breath of the Wild also has cookingelements, so maybe the gameplay is not so novel.... | Read more »
ChordFlow (Music)
ChordFlow 1.0.0 Device: iOS Universal Category: Music Price: $6.99, Version: 1.0.0 (iTunes) Description: ChordFlow is a chord sequencer with a unique 4-track polyphonic arpeggiator, extensive chord library, MIDI out and Ableton Link... | Read more »
The Walking Dead: A New Frontier is out...
The newest season of Telltale Games'The Walking Dead is well underway. After the release of the third episode, "Above the Law" about a month ago, episode four, "Thicker Than Water" is hot and ready for more zombies and gut-wrenching emotional... | Read more »
Best games we played this week
Another week, another new wave of mobile games do dive into. We've dug through the list of apps that came out this week to tell you which apps are worth your sweet time. And while there weren't too many games this week, there were some big ones.... | Read more »
Vignettes (Games)
Vignettes 1.0.1 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.1 (iTunes) Description: Vignettes is a casual but unique exploration game without text or characters, where objects shapeshift as you spin them around... | Read more »
Get Me Outta Here is an 80s retro shoote...
Are you ready to fight some aliens? Because Crescent Moon Games has released the retro shooter Get Me Outta Here on iOS devices today. [Read more] | Read more »
Get a bunch of Apple productivity apps f...
If you're an Apple Mac owner, you're probably aware of the host of Apple productivity apps the company includes in all new Mac purchases. Apps like iMovie, Keynote, and of course, GarageBand. While you used to be able to also buy these apps... | Read more »
Terra Mystica (Games)
Terra Mystica 1.03 Device: iOS Universal Category: Games Price: $9.99, Version: 1.03 (iTunes) Description: Short Summary:≈≈≈≈≈≈≈≈≈≈≈≈≈ | Read more »
Ms. Spell (Games)
Ms. Spell 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: Cast spells and battle monsters in this turn based game, that has you delving into ever the changing Dreadwood to retrieve the lost... | Read more »

Price Scanner via MacPrices.net

15-inch Touch Bar MacBook Pros, Apple refurbi...
Apple is offering Certified Refurbished 2016 15″ Touch Bar MacBook Pros for $360 to $420 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 15″ 2.6GHz... Read more
13-inch MacBook Airs on sale for up to $150 o...
Overstock.com has 13″ MacBook Airs on sale for up to $150 off MSRP including free shipping: - 13″ 1.6GHz/128GB MacBook Air (sku MMGF2LL/A): $869.99 $130 off MSRP - 13″ 1.6GHz/256GB MacBook Air (sku... Read more
15-inch Touch Bar MacBook Pros on sale for $1...
B&H Photo has the new 2016 15″ Apple Touch Bar MacBook Pros in stock today and on sale for up to $200 off MSRP. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.7GHz Touch Bar... Read more
15-inch 2.7GHz Touch Bar MacBook Pros on sale...
Amazon has 2016 15″ 2.7GHz Apple Touch Bar MacBook Pros in stock today and on sale for $150-$200 off MSRP. Shipping is free: - 15″ 2.7GHz Touch Bar MacBook Pro Space Gray (sku MLH42LL/A): $2599 $200... Read more
Apple now offering Certified Refurbished 13-i...
Apple is now offering Certified Refurbished 2016 13″ Touch Bar MacBook Pros for $270-$300 off original MSRP. An Apple one-year warranty is included with each model, and shipping is free: - 13″ 2.9GHz... Read more
MyGiHealth Digestive Symptom Tracker Version...
My Total Health, Inc. has announced the release of MyGiHealth 2.1, an important update to their digestive symptom tracker developed exclusively for iPhone, iPad and iPod touch devices. MyGiHealth is... Read more
Galaxy S8 Materials Costs Highest by Far Comp...
The new Samsung Galaxy S8 equipped with 64 gigabytes (GB) of NAND flash memory carries a bill of materials (BOM) cost that comes out to US$301.60, much higher than for previous versions of the... Read more
iCarMode 4.0 Car Dashboard App For iOS Integr...
Indie developer Diego Resnik has announced the release of iCarMode 4.0, an update to his productivity app developed for iOS devices. iCarMode has positioned itself as a true car dashboard app,... Read more
How to save $150+ on Apple’s 13-inch 2.0GHz n...
Apple Authorized Reseller B&H Photo has non-Touch Bar 13″ 2.0GHz MacBook Pros on sale for $150 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.0GHz... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more

Jobs Board

*Apple* Mac Computer Technician - GeekHampto...
…complex computer issues over the phone and in person? GeekHampton, Long Island's Apple Premium Service Provider, is looking for you! Come work with our crew Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Music Marketing Lead, iTunes & *Apple*...
# Music Marketing Lead, iTunes & Apple Music Job Number: 56868140 Culver City, California, United States Posted: Apr. 17, 2017 Weekly Hours: 40.00 **Job Summary** Read more
*Apple* Media Products - Commerce Engineerin...
Apple Media Products - Commerce Engineering Manager Job Number: 57037480 Santa Clara Valley, California, United States Posted: Apr. 18, 2017 Weekly Hours: 40.00 Job Read more
*Apple* Mac Computer Technician - GeekHampto...
…complex computer issues over the phone and in person? GeekHampton, Long Island's Apple Premium Service Provider, is looking for you! Come work with our crew Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.