TweetFollow Us on Twitter

Animation
Volume Number:3
Issue Number:7
Column Tag:Pascal Procedures

Animated BitMaps

By Scott Boyd, The MacHax™ Group, Bryan, Texas

Offscreen BitMagic

Remember the first time you picked up an object in Déja Vú or Uninvited? Absolutely dazzling! The object lifted off the page. You dragged IT around, not some gray outline. Ever wondered how animation like that is done? Obviously some of you have, because we’ve seen similar behavior in other programs. Just about all of the paint programs have it. Maybe you can think of others that do it, too.

Alan Kay loves to point out that, about ten years ago, Alto computers at XEROX Palo Alto Research Center (PARC) performed at about the same level as today’s Macintosh Plus. We now have the power to pay attention to feel and effect. Alan Kay showed a film of a current XEROX PARC project called the Alternate Reality Kit. It served as my inspiration for the effect that serves as the focus of this article. I realized that the Mac is plenty powerful to drag whole objects, and it might even be powerful enough to add shadows. The sample programs below show that my hunch was right. Try them out and see if you don’t feel a sense of excitement as the pictures leap off the screen and move around.

In addition to the samples, this article discusses some of the how-to’s of offscreen bitmap magic. It also provides you with code that you can plug directly into your programs. Now that we have a machine that can handle it, let’s push it. Let’s see what this can do for our interfaces. Figure 0 shows our example program, which allows us to drag various bitmaps without flicker.

Where do we start?

Try to imagine how you might solve the problem of dragging a picture around the screen if you could do it any way you wanted. I envision a graphics system where I could define arbitrarily-large sprites on many different planes. I could then place an object anywhere simply by specifying the x,y coordinates for the upper-left-hand corner of the sprite. OK, so we don’t have that (yet ). Frame buffers, yeah, that could do it. No? Hmm How about a hardware blitter that would also make it very fast to draw directly on the screen? We don’t have that either? All right, all right, I’ll push back my expectations a bit, and I won’t even think of mentioning the alternate screen

Fig. 0 A Moving large bitmaps flicker free

Fig. 0 B Dragging Smalll bitmaps with shadow

Suppose we do all of our drawing on the screen. No problem, right? No problem, except that it produces a very annoying side effect -- flicker. Blech! Why does flicker happen? It’s all rather simple. Basically, when you want to animate an object, you draw it somewhere, erase it, then draw it somewhere else, over and over. Now when you erase it, you have to put back whatever was underneath the object. If the user gets to watch these steps, flicker results. If you can draw everything very rapidly, then you might not even see all the drawing happen, especially if you make sure that your drawing happens during the period of time when the raster beam is travelling from the bottom to the top of the screen (the vertical blanking period). And, hey, you’ve got loads of time: 1.26 milliseconds. Of course, you get to split that time with all the VBL tasks. That’s still plenty of time to animate an icon if you use assembly language to draw directly into screen memory without QuickDraw calls. Unfortunately, that’s not nearly enough time for much else.

At this point, you might be tempted to go find a machine with the hardware support listed above. I don’t blame you, but fear not. We already know the Mac can do it, the question is, “If not the above methods, how, then?” The solution which follows seems a bit strange, but it works nicely. I think you’ll find it simple to use the code presented here in your own programs.

Living Flicker-Free: Offscreen Bitmaps

The magic ingredient that lets us avoid flicker is something called an offscreen bitmap. What you see on your Mac screen belongs to a bitmap called ScreenBits. It’s an onscreen bitmap. Offscreen means that you can’t see it. Fortunately, QuickDraw can. You can use as many offscreen bitmaps as you have memory to allocate. By drawing into them, you can prepare a scene before bringing it onto the screen with the surprisingly fast CopyBits routine. The user won’t see the drawing taking place. It leaves the user with the impression that the program is faster. For some reason, most users associate flicker with poor performance.

If you aren’t quite up to a bunch of technical details about why this works, go ahead and skip to the next section to see how you can use it.

What is a bitmap? Inside Mac defines it as a record with three fields. The first is rowBytes, which tells how many bytes of storage are needed for each row. RowBytes must be an even number because QuickDraw uses rowBytes to calculate addresses, and addresses must always be even on a 68000 (and a lot of other machines). The second is bounds, a Rect which defines the height and width and coordinate system. Third is baseAddr, a pointer to the chunk of memory rowBytes*(the height) bytes big. You get a bitmap to play with by declaring a bitmap variable and filling in all three fields. To fill in baseAddr, you must allocate the memory. See the code (function NewBitMap) for my favorite way to set up a new bitmap.

Now that you have a bitmap, how do you get to use it? Perhaps you are familiar with that field of the grafPort called portBits. By default, a grafPort has screenBits in the portBits field. You can substitute your own by using SetPortBits. Any subsequent drawing will take place in your bitmap. The primary difference is that you won’t be able to watch the drawing unless you copy that work onto the screen.

So how are we going to drag objects around? Simple. We’ll use a whole bunch of offscreen bitmaps to prepare each frame of our interactive animation. One approach requires two bitmaps the size of the screen plus one the size of the picture to drag. Before dragging begins, a copy of the screen is made into both of the screen-size bitmaps and the picture is drawn into the picture bitmap.

The following steps form the animation cycle. To repair the area where the picture was last time, copy from the virgin bitmap into the working bitmap enough area to cover over the picture. At this point, the working bitmap is identical to the virgin bitmap. Now copy the picture onto the working bitmap in the new location. The frame is complete, so copy it to the screen. The user sees, in one shot, a complete new image. The amount copied to the screen must include the area under the picture where it is now and where it was last time. If the picture is allowed to move anywhere on the screen in one step, the amount copied to the screen might need to be as large as the whole screen. If you limit movement so it can only move a limited distance each step, you can find ways to use less memory.

Fig. 1 Hypothetical App using Bitmaps

Fig. 2 Multiple bitmaps for moving trash can

This approach works well for a large number of cases. This method is very quick, but usually a memory hog. Since my interest was in dragging objects anywhere on the screen, I felt that the cost of keeping an entire extra screen-size bitmap might prove prohibitive. The second method (presented below) saves a little space at the expense of some additional copying. A third method capitalizes on the first method, but requires that you limit the movement of the object to a specified area, say a window or control’s rectangle. Then, rather than screen-sized bitmaps, you can use bitmaps the size of your limiting area. As with most programs on the Macintosh, you always have a number of ways to achieve the same result. Darin Adler of ICOM says a real Mac programmer should be able to think of at least three ways to solve a Mac problem. Maybe you can think of other approaches.

This method requires more bitmaps than the previous one, but only one screen-size bitmap to hold the copy of the screen. Another bitmap holds a picture of the object. Yet another has something new -- a shadow which can be positioned independently. Two more bitmaps round out the collection, and they act as temporary storage for what’s under the picture and the shadow.

Fig. 3 Save the bits underneath

Fig. 4 Next, draw shadow and bitmap

Here’s what happens. First, we save a copy of the bits we’re about to blast with our drawing (Figure 1). Figure 2 shows an example of putting an object and its shadow on the screen. Once we’re into the animating loop, we do the following things. The bits underneath the place where the picture and the shadow need to be saved, as shown in Figure 3. We then draw the shadow on the copy of the screen. Then we draw the picture (Figure 4). Then we show our offscreen work on the screen, as in Figure 5. Lastly, while the user isn’t looking (either because he’s too busy being amazed or maybe just because he can’t see it), we fix up the copy to look like it did before we drew on it (Figure 6). Just do this over and over, and, voíla, you’re an animation magician!

Fig. 5 Offscreen bitmap copied to screen

And now the technical description. This technique uses three phases: preparation, dragging, and cleanup.

Preparation

P0 Allocate bitmaps

P1 Erase pictureBits & shadowBits

P2 Create the region from the picture

P3 Draw the shadow into shadowBits

P3 Draw the picture into pictureBits

P4 Copy the screen into offScreenBits

Dragging

DragRect is a rectangle the size of the picture’s picFrame. It is positioned on offScreenBits where the picture is to be drawn.

D0 Save the bits underneath dragRect from offScreenBits into underBits

D1 Save the bits underneath dragRect (offset to the shadow position) into underShadowBits

D2 Draw the shadow from shadowBits into offScreenBits

D3 Draw the picture from picturebits into offScreenBits

D4 Copy portions of offScreenBits onto screenBits

D5 Replace bits underneath from underBits to offScreenBits

D6 Replace bits underneath from undershadowBits to offScreenBits

Cleanup

C0 Restore screenBits with offScreenBits

C1 Get rid of the bitmaps, regions, and grafPort

After step D6, offScreenBits is identical to screenBits before all the animation started. ScreenBits, on the other hand, is showing the picture drawn in D2-D3. Notice that the only time any drawing is done on the screen is in step D4. Change where it says “portions” to read “the intersection of the two rectangles from the current frame and the previous frame” and you can see that this is the magic step.

Here’s why: ScreenBits is showing the latest frame. We don’t want the user to see the individual drawing steps. So, two things must happen to the screen each frame. The stuff under the last position of the object must reappear and the object must appear in its new position.

The approach I chose takes the union of the two rectangles enclosing the old and new positions. That new (larger) rectangle encompasses all of the area that must change in the new frame. If the contents of the offscreen bitmap have been prepared correctly, CopyBits can copy the whole big rectangle in one call. CopyBits handles arbitrary regions well enough, but it is optimized for rectangular regions, so I chose to use UnionRect rather than UnionRgn. Since object dragging should be as realistic (speedy) as possible, every little optimization helps. By copying the minimal bounding box of the two rectangles from offScreenBits to screenBits, the old one vanishes and the new one appears all at once with a one-step update.

The Code

The code presented here includes a separate compilation unit and three sample programs, all written using MPW Pascal. The first sample program loads a picture from the resource fork, waits for a mouse click, draws the picture, then lets you drag it around until you release the button. The second sample waits for a mouseDown and then animates every picture in all open resource forks (one at a time), bouncing them around the screen. The cursor position is used to set its velocity. The third sample was written by Greg Marriott. It demonstrates how to use your own bitmaps rather than having them allocated by my code.

By plugging in your own pictures, the sample programs should give you a good idea of the responsiveness and feel you can have when you use this code in your own programs.

Fig. 6 On-screen fix-up of underneath bits

The unit DragManager has the following calls:

function InitDrag ( userOffscreenBits : bitMapPtr ) : boolean;
function NewDraggable ( thePicture : PicHandle; userPicBits, userShadowBits 
: bitMapPtr; var dragStuff : DragHandle ) : boolean;
procedure DragItTo ( dragStuff: DragHandle; mousePt: point; centered: 
boolean);
procedure DisposeDraggable ( dragStuff : DragHandle );
procedure UpdateOffScreen ( dragStuff : DragHandle; Procedure drawProc 
);
procedure CloseDrag ( disposeBitMap : boolean );

InitDrag is called once before you want to drag things around. If you pass it a bitmap, it will use that instead of allocating its own to hold the copy of the screen. InitDrag will draw on it, but you can do your own drawing on it afterwards. InitDrag returns false if it fails in any of its allocation. Call NewDraggable with the picture you want to drag around. The picture’s picFrame is used for the size of the bitmaps it will use. If your picture extends outside of its picFrame, those portions will be missing. If you pass in bitmaps, they will be drawn on, but you can change them after the call. NewDraggable returns false if it fails in any of its allocation. DragItTo positions the picture centered over a point on the screen. If you’d rather have the picture drawn below and to the right of the point, pass false for centered.

Call DisposeDraggable when you are through with the picture. It deallocates everything and removes the last picture from the screen. If you wish to leave the picture’s stuff allocated but want to remove the picture from the screen, do a DragItTo with a position way off the screen. UpdateOffScreen lets you draw on the copy of the screen. Pass it the name of a procedure you want it to call which will do your drawing. This could be useful for a variety of effects. CloseDrag disposes of the stuff InitDrag allocated.

InitDrag and NewDraggable are set up to allow you to pass in your own bitmaps rather than having them allocated automatically. If you do pass them in, be forewarned that DisposeDraggable and CloseDrag dispose of those bitmaps. You can still get rid of everything else without deleting your bitmaps if you set the appropriate bitmap baseAddr fields in your dragStuff record to nil before calling DisposeDraggable. If you want CloseDrag to get rid of the screen-size bitmap, pass in true for disposeBitMap.

I’m still looking for a chance to try using this code in other programs to replace my calls to DragGrayRgn, so bear with me if you have problems (and please let me know). For an interesting experiment, you might try using this or similar code to replace the DragGrayRgn trap.

Using Offscreen Bitmaps

This code is set up to teach you some of the tricks of non-flickering animation on the Mac. You can (and should) modify my approach to take advantage of your own data structures. For example, we here at the MacHax™ Group wrote a drawing program called Whatever (the name was unwittingly suggested by Gustavo Fernandez and Darin Adler - hi guys!). All of our drawing happens offscreen. If we chose to replace our calls to DragGrayRgn with this dragging code, we’d be crazy to go allocating more bitmaps. We keep a lot of bitmaps, pictures, and regions handy for just such an occasion.

The technique presented here requires an entire offscreen bitmap. Consider that a large screen using one bit per pixel could easily chew up over 130K. Now consider the implications of color (2, 4, and 8-bit). Clearly, a better technique is called for. I’ve found a way to use less memory, but, as in most good computer solutions, it involves the space-time tradeoff, costing almost twice as much in processor time. If you thought the above method was convoluted, you’ll be glad I didn’t write up the new method.

Related MacHax MacTips

Now here are some of the lessons learned recently here at the MacHax Group.

• Don’t use PatOr when you mean SrcOr. The effects when using CopyBits are spectacularly wrong. Since both types are really the same TYPE, the compiler let it through. Just glancing at it, it looked right. However, watching the results, it quite clearly was incorrect. Rather than OR’ing in the bitmap, it filled in some (apparently) arbitrary pattern which would change randomly every now and then. Hmm... still can’t explain it, but I can tell you to avoid it unless you like bizarre behavior.

• CopyBits respects the clipRgn and visRgn if the destination bitmap belongs to the current port. This can bite if, for example, you are using the window manager port and your bitmap crosses screenBits.bounds. You can detect such a problem when you notice that a portion of your bitmap whose boundsRect isn’t entirely within screenBits.bounds seems to be filled with garbage even after you erased the whole bitmap. You can avoid this problem by setting both the clipRgn and visRgn to the size of your offscreen bitmap long enough to do your drawing.

• DrawPicture also cares about the clipRgn and visRgn. It seems obvious, but even obvious things can slip by. Is it just me, or do other people wish the QuickDraw chapter had more in it?

• NewDraggable uses DrawPicture inside OpenRgn / CloseRgn to build a region. It is quite possible to run out of memory as the region is built. I have found pictures that will send my Mac into the weeds. Those pictures are so incredibly complex that it doesn’t surprise me at all, nor would I really want to drag them around. Just use some common sense about the pictures you want to drag around. Bitmaps are very simple, and the Drag Manager is quite capable of dragging around a full-screen bitmap.

• Don’t InvalRect or InvalRgn when your current port is a grafPort without a window. The update region is maintained as a part of the window, not the grafPort. Trying to change the updateRgn of a port without a window will mess with things on past the grafPort, resulting in unpredictable behavior. This is reason #395 in the book Why Macintosh Runs Into Trees.

• Reason #396 has to do with bitmap bounds. If they are too big, i.e. not enough memory was allocated, filling the bitmap will trash other variables that just happen to have made the mistake of moving next door. In the particular instance which taught us this lesson, three bitmaps were declared all in a row. The first one’s baseAddr size was too small when calculated, so the bitmap wasn’t allocated big enough. When we filled it with white, the next bitmap was deallocated, the heap was corrupted, and the stage was set for disaster. The next time memory was requested, the memory manager went looking for free blocks. When it hit that one, the wheels locked, the Mac went out of control, and we were unwrapping it from around a large oak.

• BUG REPORT! (subtle, huh?) As hard as we try, bugs just seem to slip through. Although no bugs in OverView (Sept. 86) were ever reported, the above investigative debugging reveals that the code used to allocate bitmaps has a problem. If (width div 16) < 8 then rowBytes is calculated two bytes too small, resulting in a bitmap allocated 2*(bottom-top) bytes too small. Somehow we’ve managed to avoid the problem, although it might explain the difficulty reported with using a certain value on a Lisa (tried once and forgotten). NewBitMap corrects this problem.

Programming grains of sand* (* pearls in the rough?)

Offscreen Bitmaps

Whenever your work involves drawing, especially any kind of interactive drawing or animation, offscreen bitmaps provide a mechanism for smooth, flicker-free drawing. While CopyBits penalizes you (albeit very slightly) by chewing up some time to get the changed material from the offscreen bitmap into screenBits, users generally perceive a performance enhancement. In addition, responding to update events requires no more than a single CopyBits if you have the full screen or window offscreen. As with most computer science problems, this trades off a little time and space for very fast response times. If you have the memory to spare, this beats watching something like PageMaker or MacDraw redraw all five grillion little objects while you sit and wait to get back to work. When it comes to watching those tedious updates, any program that doesn’t use offscreen bitmaps is wasting its time. No, strike that, wasting our time. As you will see in the next paragraph, it also gives you a ‘free’ screen to draw on. Fast updates aren’t the only benefit, you can also let your window contents keep up while scrolling.

Take a look at your data

View your data structures when you have questions about their behavior. This especially holds true if you are working with structures that have a useful visual interpretation. To make sure that my offscreen bitmaps were behaving themselves, I copied them onto the screen after drawing into them. This technique helped me track down the two biggest problems I had with developing this code. The last bad problem was that I was copying from shadowBits to the offscreen bitmap. I had generalized some of my code and things stopped working (see below). By copying shadowBits to screenBits directly, I discovered that shadowBits held the correct stuff, so the problem was narrowed down to the CopyBits call where I discovered the patOr/srcOr problem above.

Now where does that free screen come from? Since I put shadowBits directly on the screen, I want to be sure that the screen won’t be destroyed. That’s easy. Remember that I have a copy of the screen in offScreenBits. All my drawing takes place offscreen, so screenBits is a good place to do debug-drawing.

Generalize your code

This is the hard part. I had essentially what you see here running in one evening. Most good ideas happen that way. However, to present it to you in a clear fashion required much more work. I’m going to try to convince you that polishing the code is worth the effort.

For one thing, if you polish it, use it in a program, and write up an article, you can publish it, and that benefits your readers. For another, you’ll find that you need to reuse portions of it for some other project on down the road. If it’s a good idea, it’s worth doing right. You never know how many places you can plug in your code if it’s been generalized and documented.

If you put the effort into presenting it to others, you will find that you can still read your work a year from now. It’s the strangest feeling to wonder how to do something with pop-up windows, then to go pull out the article I wrote to see how to do it. Call it bizarre, but I’ve done that at least five times since last year. On top of that, nothing teaches as well as a good example. Thanks to Darin Adler and the many MacTutor authors who have provided our community of Mac programmers with good examples. Not that Darin documents the examples he’s given me, but working code often speaks for itself. It was his idea to write and collect solutions that can be used to extend the toolbox. It’s in that spirit that this work is offered to you.

Robusticize your code

A necessary part of generalizing your code is making it as bomb-proof as possible. You might notice that I check just about every time a memory-allocating operation is done. I don’t like having to do that. I’ve always expected an operating system to take care of failed memory allocations with an exception-handling mechanism, but the Mac (and/or Pascal’s runtime system) won’t do that for us yet. It makes the code longer and a little harder to read, but I guarantee the programs are a little harder to crash than when I first wrote them thinking, “Well, I’ll just never try passing in too big a picture.” Right. The first time I ran it in the MPW Shell with the Scrapbook DA open, the Mac found the nearest tree to smash into.

I’ve enjoyed working on this, and have a number of plans for improving it even further. If you have questions or suggestions, drop me a note at boyd@tamlsr on bitnet, S.T.BOYD on GENIE, or 3420D Sandra, Bryan TX 77801 on USnailNet. I would also be very interested to see your solutions and how you use them. And, naturally, I couldn’t end this without thanking TECHNOSTUD himself (aka Steve Knouse of Apple Computer) for his relentless browbeating and support.

UNIT DragManager;
{Version 1.0 Saturday, April 18, 1987 by Scott T. Boyd of
 the MacHax™ Group.  Many thanks to Greg Marriott, also a
 member of the MacHax Group. © 1987 by The MacHax Group, 
 Bryan, TX.}
{$D+} {put in debug names}
{$R+} {range checking on}
{$OV+} {overflow checking on}
{$N+} {pass routine names to linker}
INTERFACE
USES {$LOAD pinterfaces.dump} 
 MemTypes,QuickDraw,OsIntf,PasLibIntf,ToolIntf,
 PackIntf,IntEnv,CursorCtl;

type 
 shadowRecord = record
 visible : boolean;
 dx, dy : integer;
 thePattern : Pattern;
 copyMode : integer;
 end;
 dragHandle = ^dragPtr;
 dragPtr = ^dragRecord;
 dragRecord = record
 shadowBits,{the shadow}
 underShadowBits,{what’s under the shadow}
 underBits, {bits obscured by picture}
 pictureBits : BitMap;{the picture}
 shadowRegion,
 thePictureRgn : RgnHandle;{use this for masking}
 end;
 bitMapPtr = ^BitMap;
var
 shadowStuff : shadowRecord;

function InitDrag ( 
 userOffscreenBits : bitMapPtr ) : boolean;
function NewDraggable ( thePicture : PicHandle; 
 userPicBits, userShadowBits : bitMapPtr;
 var dragStuff : DragHandle ) : boolean;
procedure DragItTo ( 
 dragStuff: DragHandle; mousePt: point; 
 centered : boolean );
procedure DisposeDraggable ( dragStuff : DragHandle );
procedure UpdateOffScreen ( dragStuff : DragHandle; 
 Procedure drawProc );
procedure CloseDrag ( disposeBitMap : boolean );

IMPLEMENTATION
const
 shadow_x = 4;
 shadow_y = 6;
var
 offScreenBits : BitMap;  {the picture}

 wMgrPort,
 oldPort,
 offPort : GrafPtr;
 
 updateRegion :  RgnHandle;
 
 lastRect,{this + dragRect => updateRegion}
 otherUpdateRect,{updateRect clipped to include only           
  bits onscreen}
 updateRect,{union of dragRect and lastRect}
 dragRect,{size of the picture & centered 
  over cursor}
 tempBounds : Rect;{used in creating bitmaps}
 
 mousePt, {used to position dragRect}
 otherMousePt,   {used to see if cursor has moved}
 testPt : Point; {used to see if cursor has moved}
 
 synchCount : longint;  {wait for TickCount to change          
  before drawing}

procedure Debugger; INLINE $A9FF;
 
function NewBitMap( var theBitMap : BitMap;  
 theRect : Rect ): ptr;
begin
 with theBitMap, theRect do
 begin
 rowBytes := ((right-left+15) DIV 16) * 2;
 baseAddr := NewPtr(rowBytes * longint(right - left));
 bounds := theRect;
 if MemError <> noErr then NewBitMap := nil
 else NewBitMap := baseAddr;
 end;
end; {NewBitMap}

function InitDrag ( userOffscreenBits : bitMapPtr ) :          
 boolean;
begin
 InitDrag := false;
{do all the work in the wMgrPort}
 GetPort( oldPort );
 GetWMgrPort( wMgrPort );
{now make an offscreen bitmap to hold the whole screen}
{if one already exists, ignore what they pass in}
 if userOffScreenBits <> nil
 then offScreenBits := BitMap(userOffScreenBits^)
 else 
 if NewBitMap(offScreenBits,screenBits.bounds) = nil
 then exit( InitDrag );

{make the grafport to play with.  this way I can really
 trash up the grafport and not worry about saving and
 restoring the old one}
 offPort := GrafPtr( NewPtr( sizeof( GrafPort ) ) );
 if offPort = nil then 
 begin
 DisposPtr( offScreenBits.baseAddr );
 exit( InitDrag );
 end;
{set up off screen port so we can draw in it}
 OpenPort( offPort );
 RectRgn( thePort^.visRgn, thePort^.clipRgn^^.rgnBBox );

{create the region to update with}
 updateRegion := NewRgn;
 if updateRegion = nil then 
 begin
 DisposPtr( offScreenBits.baseAddr );
 ClosePort( offPort );
 DisposPtr( Pointer( offPort ));
 SetPort( oldPort );
 exit( InitDrag );
 end;
 
{set up the default shadow stuff}
 with shadowStuff do
 begin
 visible := true;
 dx := shadow_x;
 dy := shadow_y;
 thePattern := black;
 copyMode := SrcXor;
 end;
{copy the screen}
 CopyBits( screenBits, offScreenBits, screenBits.bounds, 
 offScreenBits.bounds, srcCopy, nil );
 InitDrag := true;
 
 SetPort( oldPort );
end;{InitDrag}

function NewDraggable ( thePicture : PicHandle;    userPicBits, userShadowBits: 
bitMapPtr;
 var dragStuff: DragHandle ) : boolean;
 procedure TestNil ( theThing : ptr );
 begin
 if theThing = nil then 
 begin
 DisposeDraggable ( dragStuff );
 NewDraggable := false;
 SetPort( oldPort );
 exit ( NewDraggable );
 end;
 end; {TestNil}
begin
 GetPort( oldPort );
 SetPort( offPort );
 dragStuff := DragHandle(NewHandle(sizeOf(dragRecord)));
 if dragStuff = nil then 
 begin
 NewDraggable := false;
 SetPort( oldPort );
 exit( NewDraggable );
 end;

MoveHHi( Handle( dragStuff ));
HLock( Handle( dragStuff ) );
with dragStuff^^ do begin
{try to allocate and erase the following bitmaps
 - pictureBits holds the bitmap to display
 - shadowBits holds the shadow
 - underBits holds the bits obscured by the picture
 - underShadowBits holds the bits obscured by the shadow
 - offScreenBits holds the entire screen’s image
}
{create a bitmap for the picture the size of the picframe}
 tempBounds := thePicture^^.picFrame;
 if userPicBits <> nil 
 then pictureBits := BitMap(userPicBits^)
 else TestNil ( NewBitMap( pictureBits, tempBounds ) );
{now create the drop shadow bitmap}
 if userShadowBits <> nil
 then shadowBits := BitMap(userShadowBits^)
 else TestNil ( NewBitMap( shadowBits, tempBounds ) );
{home tempBounds before setting up underBits}
 with tempBounds 
 do OffSetRect( tempBounds, -left, -top );
 TestNil ( NewBitMap( underBits, tempBounds ) );
{make the under-the-shadow bitmap the same size as the other underBits}
 TestNil ( NewBitMap( underShadowBits, tempBounds ) );
{clear out the bitmap for the picture}
 SetPortBits( pictureBits );
 EraseRect( pictureBits.bounds );
 
{make a region for the drop-shadow}
 shadowRegion := NewRgn;
 TestNil ( pointer( shadowRegion )); 
{draw the picture into pictureBits & create thePictureRgn}
 thePictureRgn := NewRgn;
 TestNil ( pointer( thePictureRgn ));
 OpenRgn;
 HLock( Handle( thePicture ));
 DrawPicture( thePicture, thePicture^^.picFrame );
 HUnlock( Handle( thePicture ));
 CloseRgn( thePictureRgn );
 if EmptyRgn( thePictureRgn ) 
 then RectRgn( thePictureRgn, thePicture^^.picFrame );
{put the picture in pictureBits}
 SetPortBits( pictureBits );
 HLock( Handle( thePicture ));
 DrawPicture( thePicture, thePicture^^.picFrame );
 HUnlock( Handle( thePicture ));

{clear out shadowBits}
 SetPortBits( shadowBits );
 EraseRect( shadowBits.bounds );
{fill in the shadow}
 CopyRgn( thePictureRgn, shadowRegion );
 PenMode( PatCopy );
 PenPat( shadowStuff.thePattern );
 PaintRgn( shadowRegion );
 OffSetRgn(shadowRegion,shadowStuff.dx,shadowStuff.dy);
 
{the bounding rect surrounding the picture in position}
 dragRect := pictureBits.bounds;
 
 SetRect( lastRect, 0,0,0,0 );
end; {with dragStuff^^}
HUnlock( Handle( dragStuff ) );
NewDraggable := true;
SetPort( oldPort );
end; {InitDrag}

procedure DragItTo ( dragStuff: DragHandle; mousePt: point; 
 centered : boolean );
begin
{we’ll do all our work in the window manager port}
 GetPort( oldPort );
 SetPort( offPort );

MoveHHi( Handle( dragStuff ) );
HLock( Handle( dragStuff ) );
with dragStuff^^ do 
begin
 otherMousePt := mousePt;
{home the region, the rect and the shadow region}
 OffSetRgn(thePictureRgn,-dragRect.left,-dragRect.top);
 OffSetRgn(shadowRegion,-dragRect.left,-dragRect.top);
 OffSetRect(dragRect,-dragRect.left,-dragRect.top);
{center the object over the cursor}
{calculate mousePt here because we know the rect is home’d}
 if centered then
 begin
 mousePt.h := mousePt.h - dragRect.right div 2;
 mousePt.v := mousePt.v - dragRect.bottom div 2;
 end;
 OffSetRect( dragRect, mousePt.h, mousePt.v );
 OffSetRgn( thePictureRgn, mousePt.h, mousePt.v );
 OffSetRgn( shadowRegion, mousePt.h, mousePt.v );

{save bits underneath the shadow and }
{then the bits under the picture}
 if shadowStuff.visible
 then begin
 {work with the rectangle around the shadow}
 with shadowStuff do OffsetRect( dragRect, dx, dy );
 {remove any part of dragRect which is off the screen}
 if SectRect( screenBits.bounds, dragRect, updateRect ) 
 then {};
 {the destination rectangle will be home’d}
 otherUpdateRect := updateRect;
 with otherUpdateRect do 
 OffSetRect( otherUpdateRect, -left, -top );
 {take the snapshot}
 CopyBits( offScreenBits, underShadowBits, 
 updateRect, otherUpdateRect, srcCopy,nil );
 {move dragRect back over the picture area}
 with shadowStuff 
 do OffsetRect( dragRect, -dx, -dy );
 end; {only if visible}
 
 {clip dragRect to inside the screen}
 if SectRect( screenBits.bounds, dragRect,updateRect ) 
 then {};
 {home the destination rect}
 otherUpdateRect := updateRect;
 with otherUpdateRect do 
 OffSetRect( otherUpdateRect, -left, -top );
 {say “cheese”}
 CopyBits( offScreenBits, underBits, 
 updateRect, otherUpdateRect, srcCopy,nil );

 {draw my object’s shadow.  
  => modification point <= if the copyMode is srcCopy 
 you should use shadowRegion to mask instead of nil.}    
  if shadowStuff.visible
  then begin
 with shadowStuff do OffsetRect( dragRect, dx, dy );
 CopyBits( shadowBits, offScreenBits, 
 shadowBits.bounds, dragRect, 
 shadowStuff.copyMode, nil );
 with shadowStuff do OffsetRect( dragRect, -dx, -dy);
 end; {only if visible}
 
 {draw my object}
 CopyBits( pictureBits, offScreenBits, 
 pictureBits.bounds,dragRect,srcCopy,thePictureRgn);

 {update what was on there plus the new position for 
  the object}
 UnionRect( dragRect, lastRect, updateRect );
 {include the shadow’s rectangle}
 if shadowStuff.visible
 then begin
 with shadowStuff do OffsetRect( dragRect, dx, dy );
 UnionRect( dragRect, updateRect, updateRect );
 with shadowStuff do 
 OffsetRect( dragRect, -dx, -dy );
 end; {only if visible}
 {clip it all to the screen boundaries}
 if SectRect(screenBits.bounds,updateRect,updateRect) 
 then {};
 RectRgn( updateRegion, updateRect );
 {wait for just the right moment}
 {i can’t see an improvement, can you?}
{synchCount := TickCount;
 repeat until TickCount <> synchCount;
}{put it on the screen}
 CopyBits( offScreenBits, screenBits, 
 updateRect, updateRect, srcCopy, nil );
 {restore bits underneath the picture}
 {remove any part of dragRect which is off the screen}
 if SectRect( dragRect,screenBits.bounds,updateRect) 
 then {};
 {use a home’d rectangle for underBits}
 otherUpdateRect := updateRect;
 with otherUpdateRect do 
 OffSetRect( otherUpdateRect, -left, -top );
 CopyBits( underBits, offScreenBits, 
 otherUpdateRect, updateRect, srcCopy, nil );
 {remember what parts of screenBits need to be fixed for
  next time}
 lastRect := dragRect;
 {restore bits underneath the shadow}
 if shadowStuff.visible
 then begin
 with shadowStuff do OffsetRect( dragRect, dx, dy );
 {clip it}
 if SectRect(dragRect, screenBits.bounds, updateRect) 
 then {};
 {home it}
 otherUpdateRect := updateRect;
 with otherUpdateRect 
 do OffSetRect( otherUpdateRect, -left, -top );
 {shoot it}
 CopyBits( underShadowBits, offScreenBits, 
 otherUpdateRect, updateRect, srcCopy, nil );
 {add to parts of screenBits to be fixed for next time}
 UnionRect( lastRect, dragRect, lastRect );
 with shadowStuff 
 do OffsetRect( dragRect, -dx, -dy );
 end;{only if shadow visible}
 
end; {with dragStuff^^}
HUnlock( Handle( dragStuff ));
SetPort( oldPort );
end; {DragItTo}

procedure DisposeDraggable( dragStuff : DragHandle );
begin {tidy up}
if dragStuff <> nil then begin
 HLock( Handle( dragStuff ) );
 with dragStuff^^ do
 begin
 if pictureBits.baseAddr <> nil 
 then DisposPtr( pictureBits.baseAddr );
 if underBits.baseAddr <> nil 
 then DisposPtr( underBits.baseAddr );
 if underShadowBits.baseAddr <> nil 
 then DisposPtr( underShadowBits.baseAddr );
 if shadowBits.baseAddr <> nil 
 then DisposPtr( shadowBits.baseAddr );
 if offScreenBits.baseAddr <> nil 
 then CopyBits( offScreenBits, screenBits, 
 offScreenBits.bounds, screenBits.bounds,
 srcCopy, nil );
 if thePictureRgn <> nil 
 then DisposHandle( Handle(thePictureRgn) );
 if shadowRegion <> nil 
 then DisposHandle( Handle(shadowRegion) );
 end; {with dragStuff}
 HUnlock ( Handle( dragStuff ) );
 DisposHandle( Handle( dragStuff ) );
end; {dragStuff <> nil}
end; {DisposeDraggable}

procedure UpdateOffScreen ( dragStuff : DragHandle; 
 Procedure drawProc );
begin
 GetPort( oldPort );
 SetPort( offPort );
 SetPortBits( offScreenBits );
 drawProc;
 SetPort( oldPort );
end; {UpdateOffScreen}

procedure CloseDrag ( disposeBitMap : boolean );
begin
 if offPort <> nil then
 begin
 ClosePort( offPort );
 DisposPtr( Pointer(offPort) );
 end;
 if updateRegion <> nil 
 then DisposHandle( Handle(updateRegion) );
 if disposeBitMap then
 if offscreenBits.baseAddr <> nil
 then DisposPtr( offScreenBits.baseAddr );
end;{CloseDrag}

END. {unit Drag}

program Fish;
{$D+} {put in debug names}
{$R+} {range checking on}
{$OV+} {overflow checking on}
{$N+} {pass routine names to linker}
USES {$LOAD pinterfaces.dump} 
 MemTypes,QuickDraw,OsIntf,PasLibIntf,ToolIntf,
 PackIntf,IntEnv,CursorCtl,
 {$LOAD}
 {$U UDrag.p}  DragManager;

var
 myPic : PicHandle;
 myRect : Rect;
 wPort: GrafPtr;
 whichPict,
 x,y,xinc,yinc : integer;
 xyPt, lastXYPt : point;
 myEventRecord : EventRecord;
 lastCount : longint;
 
 myDragStuff : DragHandle;

procedure SwimItAround;
const 
 CapsLockBit = 10;
var
 hOffset : integer;
 mousingRgn : RgnHandle;
 mousePt: Point;
begin
 with shadowStuff do 
 begin dx := 0; dy := 0;  visible := false; end;
 with myPic^^.picFrame do
 SetPt( lastXYPt, left-right,screenBits.bounds.bottom-         
 (bottom-top) div 2 );
 xyPt := lastXYPt;
 hOffset := 2;
 mousingRgn := NewRgn;
 CopyRgn( myDragStuff^^.thePictureRgn, mousingRgn );
 OffsetRgn( mousingRgn, -mousingRgn^^.rgnBBox.left,
 -mousingRgn^^.rgnBBox.top);
 OffsetRgn( mousingRgn, xyPt.h, xyPt.v );
repeat
 if button then 
 begin
 GetMouse( mousePt );
 LocalToGlobal( mousePt );
 if PtInRgn( mousePt,  mousingRgn) 
 then hOffset := 20;
 end;
 xyPt.h := xyPt.h + hOffset;
 xyPt.v := xyPt.v + (Random div 30000);
 OffsetRgn( mousingRgn, -mousingRgn^^.rgnBBox.left,
 -mousingRgn^^.rgnBBox.top);
 OffsetRgn( mousingRgn, xyPt.h, xyPt.v );
 
 DragItTo( myDragStuff, xyPt, false );
 lastXYPt := xyPt;
 {allow for FKEY access and give DAs and such time}
 if GetNextEvent( keyDownMask, myEventRecord ) 
 then {do nothing};
 SystemTask;
until xyPt.h > screenBits.bounds.right 
 + myPic^^.picFrame.right-myPic^^.picFrame.left;
end; {dragitaround}

begin{main}
 InitGraf(@thePort);
 
{standalones need to init windows, but tools shouldn’t}
 if IEStandalone then InitWindows;
 
 GetWMgrPort( wPort );
 SetPort( wPort );
 ClipRect( screenBits.bounds );
 
 if not InitDrag( nil ) then exit( Fish );
 
 myPic := GetPicture(197);
 if myPic = nil then 
 begin SysBeep(1); exit(Fish); end;
 {change up the default shadow stuff}
 with shadowStuff do
 begin
 thePattern := gray;
 copyMode := SrcOr;
 end;

 if not NewDraggable( myPic, nil, nil, myDragStuff )
 then exit(Fish);
 SwimItAround; 
 DisposeDraggable( myDragStuff );
 ReleaseResource( Handle( myPic ));

CloseDrag( true );
FlushEvents( everyEvent, 0 );

end.


program DragExample1;
{$D+} {put in debug names}
{$R+} {range checking on}
{$OV+} {overflow checking on}
{$N+} {pass routine names to linker}
USES {$LOAD pinterfaces.dump}MemTypes,QuickDraw,OsIntf,PasLibIntf,ToolIntf,
 PackIntf,IntEnv,CursorCtl,
 {$LOAD}
 {$U UDrag.p}  DragManager;

var
 myPic : PicHandle;
 myRect : Rect;
 wPort: GrafPtr;
 whichPict,
 x,y,xinc,yinc : integer;
 xyPt, lastXYPt : point;
 myEventRecord : EventRecord;
 lastCount : longint;
 
 myDragStuff : DragHandle;

procedure DragItAround;
const 
 CapsLockBit = 10;
begin
SetPt( lastXYPt, -1, -1 );
repeat
 GetMouse(xyPt);
{if the movement is far enough, the next line of code 
(commented out) can be used to make the image persist long 
enough that it doesn’t appear ghostly.  On the other hand, if the movement 
is very small, the image generally looks nice and solid and does not 
benefit from delays.}
 {if TickCount >= lastCount + 2 then}
 if not EqualPt( xyPt, lastXYPt) then 
 begin
 {if CapsLockKey, don’t show the shadow, else show it}
 if EventAvail(0, myEventRecord) then {twiddle};
 if BTST(myEventRecord.modifiers, CapsLockBit) 
 then with shadowStuff do 
 begin dx := 0; dy := 0; visible := false; end 
 else with shadowStuff do
 begin
 dx := xyPt.h div 8 - 32;
 dy := xyPt.v div 8 - 20;
 visible := true;
 end;

 DragItTo( myDragStuff, xyPt, true );

 lastXYPt := xyPt;
 lastCount := TickCount;
 end;
 {allow for FKEY access and give DAs and such time}
 if GetNextEvent( keyDownMask, myEventRecord ) 
 then {do nothing};
 SystemTask;
until button;
repeat until not button;
end; {dragitaround}

begin{main}
 InitGraf(@thePort);
 
{standalones need to init windows, but tools shouldn’t}
 if IEStandalone then InitWindows;
 
 GetWMgrPort( wPort );
 SetPort( wPort );
 ClipRect( screenBits.bounds );
 InitCursor;
 HideCursor;
 
 if not InitDrag( nil ) then exit( DragExample1 );
 
for whichPict := 1 to CountResources(‘PICT’) do
begin
 myPic := PicHandle(GetIndResource(‘PICT’, whichPict));
 if myPic = nil then 
 begin SysBeep(1); exit(DragExample1); end;
 {change up the default shadow stuff}
 with shadowStuff do
 begin
 thePattern := gray;
 copyMode := SrcOr;
 end;

 if not NewDraggable( myPic, nil, nil, myDragStuff )
 then exit(DragExample1);
 DragItAround; 
 DisposeDraggable( myDragStuff );
 ReleaseResource( Handle( myPic ));
end;

CloseDrag( true );
FlushEvents( everyEvent, 0 );

end.


program DragExample2;
{$D+} {put in debug names}
{$R+} {range checking on}
{$OV+} {overflow checking on}
{$N+} {pass routine names to linker}
USES {$LOAD pinterfaces.dump} 
 MemTypes,QuickDraw,OsIntf,PasLibIntf, ToolIntf,
 PackIntf,IntEnv,CursorCtl,
 {$LOAD}
 {$U UDrag.p}  DragManager;
var
 myPic : PicHandle;
 myRect : Rect;
 wPort: GrafPtr;
 whichPict,
 x,y,xinc,yinc : integer;
 xyPt, lastXYPt : point;
 myEventRecord : EventRecord;
 lastCount : longint;
 myDragStuff : DragHandle;

procedure Debugger; { turned off for now
 INLINE $A9FF;}
 begin
 {stub}
 end;

procedure BounceItAround;
begin
 GetMouse(xyPt);
 xinc := xyPt.h;
 yinc := xyPt.v;
{starting position}
 y := (screenBits.bounds.right-screenBits.bounds.left) 
 div 2;
 x := (screenBits.bounds.bottom - screenBits.bounds.top) 
 div 2;
{changes don’t occur until the focal point moves, so
 this makes sure it’s drawn the first time.}
 SetPt(lastXYPt, -100, -100);
repeat
 GetMouse(xyPt);
{use the cursor position to set the velocity of the
 bouncing picture.  It doesn’t change the direction,
 just the speed.}
 if xinc > 0 then xinc := xyPt.h else xinc := -xyPt.h;
 if yinc > 0 then yinc := xyPt.v else yinc := -xyPt.v;
{move the focal point}
 x := x + xinc;
 y := y + yinc;
{keep it on the screen and bounce off the edges if needed}
 with screenBits.bounds do
 if y < top then 
 begin y := top; yinc := -yinc; end
 else if y > bottom then 
 begin y := bottom; yinc := -yinc; end;
 with screenBits.bounds do
 if x < left then
 begin x := left; xinc := -xinc; end
 else if  x > right then
 begin x := right; xinc := -xinc; end;
 SetPt( xyPt, x, y );
{if the movement is far enough, the next line of code (commented out) 
can be used to make the image persist long enough that it doesn’t appear 
ghostly.  On the other hand, if the movement is very small, the image 
generally looks nice and solid and does not benefit from delays.}
 {if TickCount >= lastCount + 2 then}
 if not EqualPt( xyPt, lastXYPt) then 
 begin
 {if CapsLockKey, don’t show the shadow, else show it}
 if EventAvail(0, myEventRecord) then {twiddle};
 if BTST(myEventRecord.modifiers, 10) 
 then with shadowStuff do 
 begin dx := 0; dy := 0;  visible := false; end 
 else with shadowStuff do
 begin
 dx := xyPt.h div 8 - 32;
 dy := xyPt.v div 8 - 20;
 visible := true;
 end;
 DragItTo( myDragStuff, xyPt, true );
 lastXYPt := xyPt;
 lastCount := TickCount;
 end;
 {allow for FKEY access and give DAs and such time}
 if GetNextEvent( keyDownMask, myEventRecord ) 
 then {do nothing};
 SystemTask;
until button;
repeat until not button;
end; {bounceitaround}

begin{main}
 InitGraf(@thePort); 
{standalones need to init windows, but tools shouldn’t}
 if IEStandalone then InitWindows;
 GetWMgrPort( wPort );
 SetPort( wPort );
 ClipRect( screenBits.bounds );
 InitCursor;
 if not InitDrag( nil ) then exit( DragExample2 );
for whichPict := 1 to CountResources(‘PICT’) do
begin
 myPic := PicHandle(GetIndResource(‘PICT’, whichPict));
 if myPic = nil then 
 begin SysBeep(1); exit(DragExample2); end;
 if not NewDraggable( myPic, nil, nil, myDragStuff )
 then exit(DragExample2);
 BounceItAround; 
 DisposeDraggable( myDragStuff );
{Use this code to take a look at pictures that
 display some behavior you don’t understand.}
{repeat until not button;
 repeat until button;
 FillRect( screenBits.bounds, white );
 HLock( Handle( myPic ));
 DrawPicture( myPic, myPic^^.picFrame );
 myRect := myPic^^.picFrame;
 with myRect do
 OffsetRect( myRect, -left, -top );
 DrawPicture( myPic, myRect );
 HUnlock( Handle( myPic ));
}
 ReleaseResource( Handle( myPic ));
end;
CloseDrag( true );
end.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

NetNewsWire 5.0.4 - RSS and Atom news re...
NetNewsWire is the best way to keep up with the sites and authors you read most regularly. Let NetNewsWire pull down the latest articles, and read them in a distraction-free and Mac-like way. Native... Read more
EarthDesk 7.4.5 - $24.99
EarthDesk replaces your static desktop picture with a rendered image of Earth showing correct sun, moon, and city illumination. With an Internet connection, EarthDesk displays near-real-time global... Read more
BetterTouchTool 3.401 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
Vienna 3.5.6 :e12c952d: - RSS and Atom n...
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
WhatsApp 2.2031.5 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
Day One 4.16 - Maintain a daily journal.
Day One is an easy, great-looking way to use a journal / diary / text-logging application. Day One is well designed and extremely focused to encourage you to write more through quick Menu Bar entry,... Read more
VMware Fusion 11.5.6 - Run Windows apps...
VMware Fusion and Fusion Pro - virtualization software for running Windows, Linux, and other systems on a Mac without rebooting. The latest version includes full support for Windows 10, macOS Mojave... Read more
Alfred 4.1 - Quick launcher for apps and...
Alfred is an award-winning productivity application for OS X. Alfred saves you time when you search for files online or on your Mac. Be more productive with hotkeys, keywords, and file actions at... Read more
Dashlane 6.2032.0 - Password manager and...
Dashlane is an award-winning service that revolutionizes the online experience by replacing the drudgery of everyday transactional processes with convenient, automated simplicity - in other words,... Read more
Skype 8.63.0.76 - Voice-over-internet ph...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more

Latest Forum Discussions

See All

Motorball is a car football game from No...
A few years back Noodlecake Studios announced that they would be dipping in the multiplayer gaming realm with two different games. The first of those, Golf Blitz, released a while back and has proven to be very popular. Now, the second has arrived... | Read more »
SINoALICE's latest update introduce...
SINoALICE's latest update has now arrived, adding several fan-favourite characters from popular RPG series NieR. Young Nier, Kaine, and Young Emil are available in-game as part of a limited-time crossover event set to run until August 20th. [Read... | Read more »
Rocat Jumpurr is an intense roguelite pl...
Rocat Jumpurr is a roguelite platformer from developer Mousetrap Games. You might already be familiar with it if you follow the Big Indie Pitch, where it won first place during this year's Pocket Gamer Connects London competition. Following its... | Read more »
PUBG Mobile's Play As One campaign...
Back in mid-July, we reported that PUGB Mobile had teamed up with Direct Relief to help raise money for the charity's COVID-19 response project. It focused on an in-game running challenge for players, which lead to the PUBG Mobile donating $2... | Read more »
Marvel Contest of Champions' latest...
Marvel Contest of Champions' latest motion comic has arrived, and it shows off new fighters Air-Walker and Dragon Man. Both characters are set to arrive in-game this month. [Read more] | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Global Spy is an intriguing 2D spy sim f...
Developer Yuyosoft Innovations' Global Spy launched last month for iOS and Android, though if you missed it at the time, we're here to tell you why it's well worth a go. This one's all about international espionage, tracking down elusive spies,... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
Hyena Squad is sci-fi turn-based strateg...
Wave Light Games has just revealed its latest release, Hyena Squad, a turn-based RPG set in a space station infested by gross aliens and the living dead. The announcement was first reported on by Touch Arcade. [Read more] | Read more »
Idle Guardians: Never Die is a pixel art...
SuperPlanet has been fairly prolific with game releases so far this year with both Evil Hunter Tycoon and Lucid Adventure releasing earlier this year. Now, they've released another idle RPG called Idle Guardians: Never Die, which you can download... | Read more »

Price Scanner via MacPrices.net

Save hundreds of dollars on a custom-configur...
Save up to $920 on a custom-configured 16″ MacBook Pro with these Certified Refurbished models that Apple has restocked today. Each MacBook Pro features a new outer case, free shipping, and includes... Read more
New 2020 12.9″ iPad Pros on sale for up to $8...
Apple reseller Expercom has new 2020 Apple 12.9″ iPad Pros on sale today for $60-$85 off MSRP, with prices starting at $939. These are the same iPad Pros sold by Apple in their retail and online... Read more
Woot offers numerous 2018-2020 MacBook Pros a...
Amazon-owned Woot has many open-box return MacBook Airs and MacBook Pros available today at prices starting at $879. Shipping is free for Prime members. Here’s what they have as of this post, and... Read more
Apple restocks refurbished 2020 13″ MacBook A...
Apple has restocked Certified Refurbished 2020 13″ MacBook Airs starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a standard Apple one-... Read more
Apple restocks clearance 2019 13″ 2.4GHz MacB...
Apple has restocked Certified Refurbished 2019 13″ 2.4GHz 4-Core Touch Bar MacBook Pros starting at $1359 and up to $560 off original MSRP. Apple’s one-year warranty is included, shipping is free,... Read more
Apple restocks refurbished iPhone XR models s...
Apple has restocked Certified Refurbished, unlocked, iPhone XR models in the refurbished section of their online store starting at $539. Each iPhone comes with Apple’s standard one-year warranty,... Read more
Price drops! $100-$200 off clearance 27″ 5K i...
B&H Photo has dropped prices on clearance, previous-generation 27″ 5K iMacs by up to $200 off Apple’s original MSRP: – 27″ 3.0GHz 6-Core 5K iMac: $1699 $100 off original MSRP – 27″ 3.1GHz 6-Core... Read more
Woot offers Apple Watch and iPhone models fro...
Amazon-owned Woot has refurbished Apple Watch and iPhone models available from $99-$749 through August 6th. According to Woot, the items may show some wear, but they have all been fully tested and... Read more
Apple’s Phil Schiller Steps Down As SVP OF Wo...
NEWS: 08.05.20 – Former Apple senior Vice President of worldwide marketing, Phil Schiller, is stepping down from his long time role at the company in order to focus on spending more time with family... Read more
Expercom offers $320 discount on the 6-core 1...
Apple reseller Expercom has the Silver 16″ 6-core MacBook Pro on sale for a limited time for $2079 shipped. Their price is $320 off Apple’s MSRP for this model, and it’s the cheapest price currently... Read more

Jobs Board

Blue *Apple* Cafe Student Worker - Fall - P...
…to enhance your work experience. Student positions are available at the Blue Apple Cafe. Employee meal discount during working hours is provided. Duties include food Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 4 days ago Requisition # 122305 Sign Up Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 3 days ago Requisition # 122305 Sign Up Read more
Executive Team Leader GM Sales (Assistant Man...
…(Assistant Manager General Merchandise and Operations) - Apple Valley, CaliforniaApply NowJob ID:R0000082364job family:Store Managementschedule:Full Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 2 days ago Requisition # 122305 Sign Up Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.