/*
* 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:(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... */
linesTime!0@ry = DPSAddTimedEntry(0.0, &DrawIt, self,
NX_BASETHRESHOLD);
running = YES;
}

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
{
int count;

/* fill with the background color */
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!0A 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.

[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:&dummyEven!0B waitFor:0.0
threshold:NX_BASETHRESHOLD] == NULL);

[self unlockFocus];

return self;
}

@end