dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)
Here is the Rascal source code for ZoomIdle1.1 --- cut --- Program ZoomIdle; (* 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. To turn into a desk accessory: Compile and link ZoomIdle1.1.src. Execute DeskMaker (from within Rascal). Choose "Object -> DA" from the "Make" menu. When you get the configuration dialog, change the window type to -1 (no window). During testing from within Rascal, the statement that sets the window's windowKind field should be taken out. 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 53706 UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois ARPA: dubois@easter dubois@unix.macc.wisc.edu *) Uses __QuickDraw __ToolTraps __OSTraps __DeskLib (*$U+*) uToolIntf; (* includes uQuickDraw *) Link __OSTraps __DeskLib : ; EventMask $102; (* mouse-down + activate *) (* 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. *) Const zoomSteps = 15; zoomShow = 15; Var thePort: WindowPtr; theMenuBar: Handle; zRect: Rect[zoomShow]; zIndex: Integer; srcRect: Rect; zoomAway: Boolean; scrnSizeX: Integer; (* size of screen in pixels *) scrnSizeY: Integer; (* return integer between zero and max (inclusive). assumes max is non-negative. *) Function Rand (max: integer): integer; Var t: Register Integer; { t := Random (); if t < 0 then t := -t; Rand := t % (max + 1); }; (* Interpolate one rectangle smoothly into another. Each rectangle is drawn twice. 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. *) Proc ZoomRect (r1, r2: Rect); Var r1left, r1top: Register Integer; hDiff, vDiff, widDiff, htDiff: Integer; l, t: Register Integer; r, b: Integer; rWid, rHt: Integer; j: Register Integer; rp: ^Rect; { 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. *) loop (, j := 1, ++j, j > zoomSteps) { ++zIndex; if zIndex >= zoomShow then 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); }; }; (* Set up to allow zooming. Change the visRgn of the grafPort so can draw in the menu bar area. Do NOT blank the screen yet, since some programs (e.g., MacWrite, DiskInfo) mess with the menu bar once they figure out that they're not the front window anymore - if the screen is blanked here, they will mess up the menu bar. Must wait for the activate event to do that. On receipt of the activate event, clear the menu bar so that MenuSelect (which will be called from the main application) doesn't start merrily pulling down menus when the mouse is clicked in the menu bar. Clicking in a cleared menu bar will cause MenuSelect to do nothing but retain control until the button is released - effectively pausing the display. Initialize the rectangle array to a bunch of empty rects. This must be done for the zoom algorithm to work correctly, because one rect is erased for every one drawn. If they are set empty, the first time they are erased, nothing changes on the screen. *) Proc _Init (); { GetWMgrPort (@thePort); srcRect := thePort^.portRect; scrnSizeX := srcRect.right; scrnSizeY := srcRect.bottom; thePort := NewWindow (nil, srcRect, "", true, noGrowDocProc, -1L, true, 0L); RectRgn (thePort^.visRgn, srcRect); WindowPeek (thePort)^.windowKind := GetDARefNum (); (* Initialize the rect array. This also sets zIndex to a value that causes it to reset to zero on the first call to ZoomRect. *) loop (, zIndex := 0, ++zIndex, zIndex >= zoomShow) SetRect (zRect[zIndex], 0, 0, 0, 0); zoomAway := false; }; (* When an activate event is received, go ahead and clear the menu bar, paint the screen black, and start zooming. The ValidRect is to prevent an update event - without it everything below the menu bar gets painted white - presumably this is done by the DA wrapper provided by DeskMaker? The test to check that the activate event is really an activate and not a deactivate is necessary when testing within Rascal. Since the program will be treated as an application, it will get events other than those for just it's own window - in particular, the deactivate event for the previous window gets seen here. If the check isn't made, this is what happens: the deactivate comes in, the menu bar gets cleared the screen is painted and the pattern and mode are changed. Then the activate comes in, the menu bar is cleared again (clobbering what was just saved!) and the screen is painted again - gray this time since the penpat was just changed. Quit on first mouse click in executor window. Hide the window so all other windows will get updated. Restore the menu bar as well. *) Proc _Event (theEvent: EventRecord); Const activeFlag = 1; Var pat: Pattern; A4: PtrB; { case theEvent.what of activateEvt: { if theEvent.modifiers and activeFlag then { theMenuBar := GetMenuBar (); ClearMenuBar (); SetPort (thePort); PaintRect (srcRect (* = thePort^.portRect *)); ValidRect (srcRect); PenMode (patXor); (* do all drawing in xor *) PtrL (pat)^ := $aa55aa55L; (* with gray pattern *) PtrL (pat + 4)^ := $aa55aa55L; PenPat (pat); zoomAway := true; (* window active - can go ahead now *) }; }; mouseDown: { CloseWindow (thePort); (* cause other windows to be updated *) SetMenuBar (theMenuBar); (* restore menu bar *) DisposHandle (theMenuBar); DrawMenuBar (); Push (reg A4.L); Pop (A4); A4[-17] := 1; (* halt program *) }; end; }; Proc _Main (); Var dstRect: Rect; pt1, pt2: Point; { if zoomAway then { SetPort (thePort); ObscureCursor (); (* keep cursor hidden *) pt1.h := Rand (scrnSizeX); (* generate rect, zoom to it *) pt1.v := Rand (scrnSizeY); pt2.h := Rand (scrnSizeX); pt2.v := Rand (scrnSizeY); Pt2Rect (pt1.vh, pt2.vh, dstRect); ZoomRect (srcRect, dstRect); srcRect := dstRect; }; }; --- end ---
dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)
Here is the LightspeedC source to ZoomIdle1.1. For printing, tab size should be 4. --- cut --- /* 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 53706 UUCP: {allegra, ihnp4, seismo}!uwvax!uwmacc!dubois ARPA: dubois@easter dubois@unix.macc.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); } --- end ---
dubois@uwmacc.UUCP (Paul DuBois) (07/26/86)
ZoomIdle Commentary There are some assumptions inherent in the code. These are not always explicitly documented; I discuss them here. This discussion applies both to the Rascal and the LightspeedC source versions. When ZoomIdle is opened, a window is put up over the screen. The portRect of the Window Manager port is used so it will work for a screen of any size. The visRgn of the port must be set to the entire screen or drawing will only take place below the menu bar. Then ZoomIdle waits. Two event types are processed: activate and mouse down. accRun messages are also processed. (accRun code chooses a rectangle randomly and zooms to it; that code is not triggered until an activate event for the window is received.) Since the Event Manager reports window deactivation before window activation, we know, when the activate event arrives, that the previous active window (whatever it was) has been deactivated and its owner did whatever was necessary to respond to that. ZoomIdle clears the menu bar to prevent the main application from allowing menu item selection, paints the screen black, and sets the state so that zooming begins (i.e., so that accRun messages aren't ignored). Mouse clicks in the menu bar are not reported to ZoomIdle, because applications typically pass the click to MenuSelect to track the mouse until the button is released. With the menu bar cleared, MenuSelect just waits until the mouse button is released. The effect is simply to pause the display while the button is down. (In particular, ZoomIdle can't be selected again; that's why there isn't any check in the code to see if the driver is already open. Normally, it's dangerous not to make this check.) Mouse clicks below the menu bar are reported to ZoomIdle, and cause termination. The window is destroyed and the menu bar is restored. Note that there is no check to see if the menu bar actually needs restoring. It is reasonable to ask whether this is valid. For instance, if the user clicks the mouse after initiating ZoomIdle but before the window comes active, will there be any menu bar to restore? Yes, because ZoomIdle takes advantage of the fact that the perceptual mechanisms of the Macintosh operating system induce a distortion of temporal reality; events are not reported in chronological order but in priority order. Since activates are higher priority than mouse clicks, no matter when the user clicks the mouse, ZoomIdle can never become conscious of the click until after the activate event is received. Hence, it *must* be true that the menu bar needs restoring when the mouse click handling code is invoked. The drawing pattern and mode for zooming are set to gray and patXor, respectively. ZoomIdle builds its own gray pattern because, as a DA, it doesn't access off A5. I don't know what that means, except that the normal QuickDraw globals can't be used.