This is SpaceView.m in view mode; [Download] [Up]
// SpaceView.m // // This class implements the flying starfield screen saver view. // // 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. #import "SpaceView.h" #import "psfuncts.h" #import <appkit/Application.h> #import <dpsclient/wraps.h> #import <appkit/NXImage.h> #import <objc/zone.h> #import <mach/mach.h> #import <c.h> #import <libc.h> #import <math.h> #define PI (3.141592653589) // used in producing timed events static void AnimateSpace(); DPSTimedEntry aniSpaceTag; // This should return a float between 0 and 1 float frandom() { float val = (random() & 0x7fffffff); val /= 0x7fffffff; return val; } float randBetween(float a, float b) { float val, scale, t; if (a > b) { t = a; a = b; b = t; } scale = (b-a); val = scale * frandom(); return (a + val); } @implementation SpaceView //takes theta and distance and stuffs it into x &y for *p - convertToXY:(STAR *)p { p->draw->x = floor(bounds.size.width / 2 + (p->distance * cos(p-> theta))); p->draw->y = floor(bounds.size.height / 2 + (p->distance * sin(p-> theta))); return self; } - oneStep { int i, count, starsInArray = 0; STAR *p; NXPoint *t; if (nstars < NSTARS) [self addStar]; for (i=0; i<nstars; i++) { p = &stars[i]; p->distance += p->delta; p->delta *= p->ddelta; [self convertToXY:p]; // only draw the star if it moved > 1 pixel if (p->draw->x != p->erase->x || p->draw->y != p->erase->y) { BOOL mustErase = NO; // add star to the erasure array b[starsInArray] = *p->erase; bc[starsInArray] = p->c; if (p->distance > p->changepoint[p->changemode]) { (p->c)++; // increment character for next star size (p->changemode)++; } // clipping is off, so we must not draw outside view. // replace stars that go too far... if (p->draw->x < 0 || p->draw->y < 0 || p->draw->x + 7 > bounds.size.width || p->draw->y + 7 > bounds.size.height) { [self replaceStarAt:i]; mustErase = YES; } w[starsInArray] = *p->draw; wc[starsInArray] = p->c; if (mustErase || [self allowStars:p]) starsInArray++; t = p->draw; p->draw = p->erase; p->erase = t; } } bc[starsInArray] = wc[starsInArray] = 0; //null terminate string if (starsInArray) { for (i=0; i<(starsInArray-1); i++) { bOffsets[i].x = b[i+1].x - b[i].x; bOffsets[i].y = b[i+1].y - b[i].y; wOffsets[i].x = w[i+1].x - w[i].x; wOffsets[i].y = w[i+1].y - w[i].y; } bOffsets[i].x = bOffsets[i].y = wOffsets[i].x = wOffsets[i].y = 0; count = 0; while (count < starsInArray) { char tc; int j; // You get the best performance if you put out all the stars // at once. This causes noticable flicker, so I put out // 100 of the stars per iteration. This gives reasonable speed // and flicker is hardly noticable. Besides, stars // _should_ flicker a little... int t = (starsInArray - count); i = (t < STARSPERIT)?t:STARSPERIT; j = i + count; PSsetgray(NX_BLACK); tc = bc[j]; bc[j] = 0; PSWXYShow(b[count].x, b[count].y, &bc[count], (float *)(&bOffsets[count].x), i*2); bc[j] = tc; PSsetgray(NX_WHITE); tc = wc[j]; wc[j] = 0; PSWXYShow(w[count].x, w[count].y, &wc[count], (float *)(&wOffsets[count].x), i*2); wc[j] = tc; count += STARSPERIT; } } return self; } // returns yes if the star is outside the avoidance rectangle // this is really fast and loose but it works acceptibly well // ps I could just use NXIntersectsRect() but I want to avoid // trap overhead. Call me paranoid... - (BOOL) allowStars:(const STAR *)p { // just return if voidRect not set if ((!voidRect.size.width) || p->draw->x < voidRect.origin.x || p->draw->y < voidRect.origin.y || p->draw->x+7 > voidRect.origin.x+voidRect.size.width || p->draw->y+7 > voidRect.origin.y+voidRect.size.height || p->erase->x < voidRect.origin. x || p->erase->y < voidRect.origin. y || p->erase->x+7 > voidRect.origin.x+voidRect.size.width || p->erase->y+7 > voidRect.origin.y+voidRect.size.height) return YES; return NO; } - initFrame:(const NXRect *)frameRect { [super initFrame:frameRect]; [self allocateGState]; // For faster lock/unlockFocus [self setClipping:NO]; // even faster... [self setRadius]; loadPSProcedures(); PSWDefineFont("StarFont"); return self; } - drawSelf:(const NXRect *)rects :(int)rectCount { // this drawself doesn't really draw the view at all. // in fact it just promotes the window to screen depth... NXRect t = {0,0,1,1}; PSsetrgbcolor(1,0,0); NXRectFill(&t); //yucky trick for window depth promotion! PSsetgray(NX_BLACK); NXRectFill(&t); PSselectfont("StarFont", 1.0); return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; if (oldSize.width != bounds.size.width || oldSize.height != bounds.size.height) { oldSize.width = bounds.size.width; oldSize.height = bounds.size.height; [self setRadius]; nstars = 0; [self display]; } return self; } // only call addStar if there is room in the stars array! - addStar { [self replaceStarAt:nstars++]; return self; } - replaceStarAt:(int)index { float dist, t; int tries = 0; STAR *p = &stars[index]; BOOL inBounds; p->draw = &p->r1; p->erase = &p->r2; do { p->theta = randBetween(0,(2*PI)); if (tries++ < 3) p->distance = randBetween(1, radius); else p->distance = randBetween(1, p->distance); inBounds = YES; [self convertToXY:p]; if (p->draw->x < 0 || p->draw->y < 0 || p->draw->x + 7 > bounds.size.width || p->draw->y + 7 > bounds.size.height) { inBounds = NO; } } while (!inBounds); p->delta = (0.1); p->ddelta = randBetween(1.0, 1.1); t = randBetween(0, (0.42*radius)); dist = MAX(20,t); p->changepoint[0] = p->distance + 5; // to b p->changepoint[1] = p->changepoint[0] - 5 + dist + dist; // to c p->changepoint[2] = p->changepoint[1] + dist; // to d p->changepoint[3] = p->changepoint[2] + dist; // to e p->changepoint[4] = p->changepoint[3] + dist; // to f p->changepoint[5] = 100000; // never change to g p->changemode = 0; if ((++toggle) & 1) p->c = 'a'; else p->c = 'g'; p->r2 = p->r1; return self; } - setRadius { float x = bounds.size.width; float y = bounds.size.height; radius = (sqrt(x*x + y*y))/2; return self; } - setVoidRect:(const NXRect *)r { voidRect = *r; return self; } - inspector:sender { [self display]; return self; } - (BOOL)ignoreMouseMovement { return NO; } - inspectorWillBeRemoved { return self; // just a prototype } - inspectorInstalled { return self; // just a prototype } //***************************************************************************** // // circular clipping path // //***************************************************************************** - clipToFrame:(const NXRect *)frameRect { float x, y, cradius; // Center the circle and pick an appropriate radius x = frameRect->origin.x + frameRect->size.width/2.0; y = frameRect->origin.y + frameRect->size.height/2.0; cradius = frameRect->size.height/2.0; // Create a circular clipping path PSnewpath(); PSarc(x, y, cradius, 0.0, 360.0); PSclosepath(); PSclip(); return self; } @end // this class is only used in the inspector, it animates // when it draws itself. @implementation StaticSpaceView - drawSelf:(const NXRect *)rects :(int)rectCount { int i; if (!rects || !rectCount) return self; PSselectfont("StarFont", 1.0); PSsetgray(NX_BLACK); NXRectFill(rects); for (i=0; i<20; i++) { [self oneStep]; [[self window] flushWindow]; NXPing(); } return self; } - initFrame:(const NXRect *)frameRect { [super initFrame:frameRect]; [super setClipping:YES]; while (nstars < NSTARS) [self addStar]; if(!aniSpaceTag && [[super window] title]) aniSpaceTag = DPSAddTimedEntry( // register function Animate 0.08, // to be called every period of (DPSTimedEntryProc)AnimateSpace, // arg0 (id)self, NX_BASETHRESHOLD); return self; } - sizeTo:(NXCoord)width :(NXCoord)height { [super sizeTo:width :height]; nstars = 0; while (nstars < NSTARS) [self addStar]; return self; } //***************************************************************************** // // remove the timed entry when we exit // //***************************************************************************** - free { if (aniSpaceTag) DPSRemoveTimedEntry (aniSpaceTag); aniSpaceTag = 0; return [super free]; } @end //***************************************************************************** // // This fucntion is registered by DPSaddtimedentry. // It is subsequently called every period t as registered // in arg 0 of DPSaddtimedentry. // //***************************************************************************** static void AnimateSpace(DPSTimedEntry time_tag, double now, id self) { [self display]; } @implementation View(nonretainedFillMethod) // I add this method as a category of View to be sure that all // my views implement it. I really want to use nonretained windows // but they are drawn via drawSelf at all kinds of goofy times. It // seems like the kit kind of throws up its hands when it doesn't have // a buffer to draw from, so you get a lot more drawSelfs than you need, // and sometimes you don't get them when you really want them. I know // when I need the background filled in black, so I factor that out of // my drawSelf and then drawself only draws things that are already on // screen so you don't see it happen. I will only call this method on // a nonretained (and full screen) window. - fillBoundsWithBlack { if ([self canDraw]) { [self lockFocus]; PSsetgray(NX_BLACK); NXRectFill(&bounds); [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.