/*
	ZoomIdle
	Version 1.1     25 July 1986

	Screen saver desk accessory.  Generates successive randomly chosen
	rectangles, and zooms each into the next by interpolating a series
	of rectangles intermediate in shape and location.  Click and hold in
	menu bar to pause the display.  Click below menu bar to terminate.

	The project should include MacTraps and ZoomIdle1.1.c.  The project
	type should be Desk Accessory.

	Changes since 1.0:

	Works with Macintosh XL (wider screen).
	Doesn't clear the menu bar until the window receives an activate
	event.  1.0 cleared the bar immediately, which was too early -
	some programs would crash when ZoomIdle exited.


	Paul DuBois
	Wisconsin Regional Primate Research Center
	1220 Capitol Court
	University of Wisconsin-Madison
	Madison, WI  53715-1299

	UUCP: {allegra, harvard, seismo}!uwvax!rhesus!dubois
	ARPA: dubois@primate.wisc.edu
*/


# include	<DeviceMgr.h>
# include	<WindowMgr.h>	/* includes QuickDraw.h, MacTypes.h */
# include	<EventMgr.h>
# include	<MenuMgr.h>

# define	nil			0L


/*
	zoomSteps controls the number of rectangles that are drawn in each
	interpolative series.  zoomShow controls how many rectangles are
	visible at once.  These two values may be varied independently, but
	they must both be greater than zero.
*/

# define	zoomSteps	15
# define	zoomShow	15


/*  global variables  */

Rect		zRect[zoomShow];
int			zIndex;


/*
	Return integer between zero and max (inclusive).  Assumes max is
	non-negative.
*/


Rand (max)
int	max;
{
register int	t;

	if ((t = Random ()) < 0) t = -t;
	return (t % (max + 1));
}


/*
	Interpolate one rectangle smoothly into another.  This algorithm
	assumes that it is erasing the previous series while it's
	drawing the new one.  The first time this is called, that is not
	true, which is why the init code sets all the zRect rectangles
	empty - it's harmless to erase empty rectangles (doesn't show on
	screen).
	
	Pen mode should be set to patXor, so that the first drawing shows
	the rectangle, the second time erases it.  Gray seems to work
	best for the pen pattern:  white is too stark a contrast, except
	perhaps for the very dialectic.
*/

ZoomRect (r1, r2)
Rect	r1, r2;

{
register int	r1left, r1top;
register int	l, t;
register int	j;
int				hDiff, vDiff, widDiff, htDiff;
int				r, b;
int				rWid, rHt;
register Rect	*rp;

	r1left = r1.left;
	r1top = r1.top;
	hDiff = r2.left - r1left;	/* positive if moving to right */
	vDiff = r2.top - r1top;		/* positive if moving down */
	rWid = r1.right - r1left;
	rHt = r1.bottom - r1top;
	widDiff = (r2.right - r2.left) - rWid;
	htDiff = (r2.bottom - r2.top) - rHt;

/*
	order of evaluation is important in the rect coordinate calculations.
	since all arithmetic is integer, you can't save time by calculating
	j/zoomSteps and using that - it'll usually be zero.
*/

	for (j = 1; j <= zoomSteps; j++)
	{
		if (++zIndex >= zoomShow)
			zIndex = 0;
		rp = &zRect[zIndex];
		FrameRect (rp);				/* erase a rectangle */
		l = r1left + (hDiff * j) / zoomSteps;
		t = r1top + (vDiff * j) / zoomSteps;
		r = l + rWid + (widDiff * j) / zoomSteps;
		b = t + rHt + (htDiff * j) / zoomSteps;
		SetRect (rp, l, t, r, b);
		FrameRect (rp);
	}
		
}


main(p, d, n)
cntrlParam *p;	/*  ==> parameter block  */
DCtlPtr d;		/*  ==> device control entry  */
int n;			/*  entry point selector  */

{
register DCtlPtr	dce = d;
Rect				dstRect;
Point				pt1, pt2;
long				pat[2];
static int			scrnSizeX;
static int			scrnSizeY;
static Rect			srcRect;
static Handle		theMenuBar;
static WindowPtr	theWind;
static Boolean		zoomAway = false;


	/*  check to make sure our data area was allocated  */
		
	if (dce->dCtlStorage == 0)
	{
		if (n == 0)						/*  open  */
			CloseDriver(dce->dCtlRefNum);
	}
	else switch (n)	/*  dispatch  */
	{
		case 0:		/*  open  */

			dce->dCtlFlags |= dNeedLock | dNeedTime;
			dce->dCtlDelay = 0;
			dce->dCtlEMask = everyEvent;
		
			GetWMgrPort (&theWind);
			srcRect = theWind->portRect;
			scrnSizeX = srcRect.right;	/* get size of screen */
			scrnSizeY = srcRect.bottom;
			theWind = NewWindow (nil, &srcRect, "\p", true,
									noGrowDocProc, -1L, true, 0L);
			RectRgn (theWind->visRgn, &srcRect);
			((WindowPeek) theWind)->windowKind = dce->dCtlRefNum;
		
/*
			Initialize the rect array.  This also sets zIndex
			to a value that causes it to reset to zero on the
			first call to ZoomRect.
*/
			for (zIndex = 0; zIndex < zoomShow; ++zIndex)
				SetRect (&zRect[zIndex], 0, 0, 0, 0);

			break;

		case 2:		/*  control  */

			SetPort (theWind);
			switch (p->csCode)
			{
				case accEvent:	/* put glasses on to read next switch */

					switch (((EventRecord *) * (long *) &p->csParam)->what)
					{
						case activateEvt:

							theMenuBar = GetMenuBar ();
							ClearMenuBar ();
							PaintRect (&srcRect /* = &theWind->portRect */);
							PenMode (patXor);	/* do all drawing in xor */
							pat[0] = pat[1] = 0xaa55aa55L;
							PenPat (&pat);	/* can't use QD global pats! */
							zoomAway = true;
							break;

						case mouseDown:	/* quit on mousedown event */

							CloseDriver(dce->dCtlRefNum);
							break;
					}
					break;

				case accRun:

					if (zoomAway)	/* activated */
					{
						ObscureCursor ();	/* keep cursor hidden */
						pt1.h = Rand (scrnSizeX);	/* generate rect and zoom to it */
						pt1.v = Rand (scrnSizeY);
						pt2.h = Rand (scrnSizeX);
						pt2.v = Rand (scrnSizeY);
						Pt2Rect (pt1, pt2, &dstRect);
						ZoomRect (srcRect, dstRect);
						srcRect = dstRect;
					}
					break;
			}
			break;

		case 4:		/*  close  */

			DisposeWindow (theWind);
			SetMenuBar (theMenuBar);	/* restore menu bar */
			DisposHandle (theMenuBar);
			DrawMenuBar ();
			break;
	}
	
	/*  done  */
	
	return(0);
}

