ftp.nice.ch/pub/next/text/etext/eText5-0.93.Source.NIHS.tar.gz#/eText5/Bounce.bproj/LinesView.m

This is LinesView.m in view mode; [Download] [Up]

/*
 * LinesView.m, a small sample view for showing timed entries & userpaths.
 * Author: Ali T. Ozer, NeXT Computer, Inc.
 * Written March 19, 1989.
 * Updated for 2.0 Oct 16, 1990 by Jayson Adams to use UserPath.[hm].
 * Updated for 3.0 March 24, 1992 by Ali Ozer
 *
 * You may freely copy, distribute and reuse the code in this example.
 * NeXT disclaims any warranty of any kind, expressed or implied, as to its
 * fitness for any particular use.
 *
 * LinesView draws a number of connected lines whose endpoints bounce around
 * randomly within the bounds of the view. The endpoints are stored in
 * an data array which is passed to PostScript as a user path. The
 * animation is performed by calling the "animate" method as often as
 * possible through a timed entry.
 */

#import <appkit/appkit.h>
#import <libc.h>                // For random(), etc...
#import <dpsclient/wraps.h>	// For PS and DPS function prototypes
#import "LinesView.h"

#define RANDINT(n) (random() % (n+1))		// Return random integer 0..n

#define XVEL corners[count].xVel  // Some slimy shortcuts, asuuming we're
#define YVEL corners[count].yVel  // using "count" as corner counter.
#define XLOC corners[count].xLoc
#define YLOC corners[count].yLoc

#define MAXVEL 12		// Maximum velocity of corners


@implementation LinesView

- initFrame:(const NXRect *)rect
{
    [super initFrame:rect];

  /* create a user path */
    userPath = newUserPath();
    
    running = NO;

    return self;
}

- free
{
  /* be sure to stop the timed entry */
    if (running) {
	DPSRemoveTimedEntry(linesTimedEntry);
    }
    freeUserPath(userPath);
    
    return [super free];
}

void DrawIt(DPSTimedEntry te, double timeNow, void *data)
{
  /* we set data to self so we can call this method from the timed entry */
    [(id)data animate];
}

- toggleRun:sender
{
  /* start or stop the timed entry (we're called by a two-state button) */
    if (running) {
	DPSRemoveTimedEntry(linesTimedEntry);
	running = NO;
    } else {
      /* Call the DrawIt() function as often as possible... */
	linesTimedEntry = DPSAddTimedEntry(0.01, &DrawIt, self,
					   NX_BASETHRESHOLD);
	running = YES;
    }

    return self;
}

- off
{
    if (running) {
		DPSRemoveTimedEntry(linesTimedEntry);
		running = NO;
	}
	return self;
}
/*
 * This method should be connected to a UI object capable of generating
 * int numbers. Note that to successfully detect the initial value of this
 * slider as set through IB, we also declare an outlet named "numberOfCorners,"
 * and connect it to this UI object. Thus this method (setNumberOfCorners:)
 * gets called when the .nib is being loaded, and we can detect the
 * initial value of the slider.  Because at initialization time the window
 * isn't up yet, we won't really update the display at that time, even though
 * display is called below.
 */
- setNumberOfCorners:sender
{
    int	   count;
    int    oldNumCorners = numCorners;

  /* set the number of corners based on the "corners" slider */
    numCorners = MIN(MAXNUMCORNERS, MAX([sender intValue], MINNUMCORNERS));

  /* set the new corner starting positions & velocities */
    for (count = oldNumCorners; count < numCorners; count++) {
      XLOC = (int)(bounds.size.width) / 2;      
      YLOC = (int)(bounds.size.height) / 2;      
      XVEL = (RANDINT(4) ? 1 : -1) * (1 + RANDINT(MAXVEL/2));
      YVEL = (RANDINT(4) ? 1 : -1) * (1 + RANDINT(MAXVEL/2));
    }
    [self display];

    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	NXRect r2;
    int	  count;
    
  /* fill with the background color */
	[[self superview] getBounds:&r2];
	NXDrawWhiteBezel(&bounds, &r2);
  //  NXEraseRect(&bounds);
    
    PSsetgray(NX_BLACK);
    PSsetlinewidth(0.0);

  /* "plot" the points */
    beginUserPath(userPath, NO);
    for (count = 0; count < numCorners; count++) {
	if (count) {
	    UPlineto(userPath, XLOC, YLOC);
	} else {
	    UPmoveto(userPath, XLOC, YLOC);
	}
    }
    closePath(userPath);
    
  /* draw it */
    endUserPath(userPath, dps_ustroke);
    sendUserPath(userPath);
    
    return self;
}

- animate
/*
 * Lines is an unusual animation program in that it runs untimed; that is,
 * it runs as fast as the CPU will allow, and it doesn't care that on faster
 * CPUs the animation will run faster. An animation or game application
 * will usually want to limit to frame rate to a value (for instance, 30
 * frames a second), and on hardware not capable of that rate, end up doing
 * the best it can. Such an application would also look at the time
 * that actually passed between frames and increment the animation or game play
 * accordingly. (See the sources to the BreakApp example on how it does it
 * animation timing. BreakApp also allocates a graphic state for its view so
 * that the lock/unlockFocus is faster.)
 *
 * Lines accomplishes its goal of running as fast as possible by creating
 * a timed entry with a 0.0 second period. This means that the timed entry
 * will fire and this method (animate) will be called as soon as possible.
 * To make things even faster, we stay in this method until some event comes
 * along. Staying in this method has the advantage that we do not need to
 * lock or unlockFocus every frame. Of course, this only works as desired
 * if the timed entry was placed with a period of 0.0 seconds.
 * 
 * If an event comes along, we return from this method and process the event.
 * Then, unless the user stopped the animation, the timed entry brings us
 * right back in to continue with the animation.
 *
 * Lines uses a buffered output window as a means to fake double-buffered
 * animation. The current frame is drawn directly into the window. However,
 * because the window is buffered, the drawing goes to the backing store,
 * and not the screen.  Only when the frame is complete does Lines flush the
 * window contents to the screen; this process is fast and provides a
 * flicker-free update.  The next frame is then drawn into the backing store,
 * and the cycle continues.
 */
{
    int count;  
    NXEvent dummyEvent;  // For peeking at the event queue. 
    
 	if (!running) return nil;
    [self lockFocus];
    
    do {
      /* move all the corners... */
	for (count = 0; count < numCorners; count++) {
	    XLOC += XVEL;
	    YLOC += YVEL;

	  /*
	   * Detect collision with sides; if we collide, bounce back in some
	   * random fashion.
	   */
	    if (XLOC >= bounds.size.width) {
		XLOC = bounds.size.width-1;
		XVEL = -1-RANDINT(MAXVEL);
	    } else if (XLOC < bounds.origin.x) {
		XLOC = bounds.origin.x;
		XVEL = 1+RANDINT(MAXVEL);
	    }
	    if (YLOC >= bounds.size.height) {
		YLOC = bounds.size.height-1;
		YVEL = -1-RANDINT(MAXVEL);
	    } else if (YLOC < bounds.origin.y) {
		YLOC = bounds.origin.y;
		YVEL = 1+RANDINT(MAXVEL);
	    }
	}

      /* draw our path and flush to the screen */
	[self drawSelf:&bounds :1];
	[window flushWindow];
	
    } while ([NXApp peekNextEvent:NX_ALLEVENTS
		    into:&dummyEvent 
		    waitFor:0.0
		    threshold:NX_BASETHRESHOLD] == NULL);

    [self unlockFocus];

    return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.