Interface.c

From Organic Design wiki
Legacy.svg Legacy: This article describes a concept that has been superseded in the course of ongoing development on the Organic Design wiki. Please do not develop this any further or base work on this concept, this is only useful for a historic record of work done. You may find a link to the currently used concept or function in this article, if not you can contact the author to find out what has taken the place of this legacy item.
// [[[[http://www.organicdesign.co.nz/peerd|peerd]]]] - nodal p2p wiki daemon
// This article and all its includes are licenced under LGPL
// GPL: [[[[http://www.gnu.org/copyleft/lesser.html]]]]
// SRC: [[[[http://www.organicdesign.co.nz/interface.c]]]]
// included in [[[[http://www.organicdesign.co.nz/category:peerd/files/C|peerd]]]][[[[http://www.organicdesign.co.nz/peerd.c|/peerd.c]]]]
// See also: [[[[http://www.organicdesign.co.nz/talk:interface.c|talk page]]]], [[[[http://www.libsdl.org/index.php|libSDL.org]]]], [[[[http://www.libsdl.org/cgi/docwiki.cgi|SDL Wiki]]]]
// Currently working on [[[[recursive rectangles]]]] milestone

// Prototypes
void ifInit();
void ifExit();

void frame();
void click();
void render();

void spriteInit();
void spriteExit();

void audioInit();
void audioMain(void *udata,Uint8 *stream,int len);

void videoInit();
void videoResize();
SDL_Surface *videoLoadImage(char *file);

// spriteInfo structure to represent a nodal [[[[layer]]]]
typedef struct {
	int index,children;
	int x,y,w,h,t;
	double scale,rotation;
	Uint32 fill;
	SDL_Surface *image;
	TTF_Font *font;
	char *text;
	} spriteInfo;

// SDL globals
node n;
SDL_Surface *surface, *test_image;
SDL_Event event;
int bpp = 0, surfaceW = 640, surfaceH = 480, f = 0;
int flags = SDL_HWSURFACE|SDL_RESIZABLE|SDL_DOUBLEBUF;
int zorder;
TTF_Font *test_font;

// Audio test - see [[[[http://www.libsdl.org/cgi/docwiki.cgi/Audio_20Examples|SDL Audio Examples]]]]
SDL_AudioSpec *wav;
Uint32 wavLen, wavCtr;
Uint8 *wavBuf, *wavPtr;


// ----------------------------------------------------------------------------------------- //
// interface.c

void ifInit() {

	// Hook render-workflow into [[[[desktop]]]]
	*nodeState(nodeRENDER,nodeCODE) = &render;
	nodeLoopInsert(nodeDESKTOP,nodeRENDER);

	// Set up a spriteInfo struct for [[[[desktop]]]] children to base their metrics on
	spriteInfo *sprite = malloc(sizeof(spriteInfo));
	sprite->index = 0;
	sprite->children = 0;
	sprite->rotation = 0;
	sprite->scale = 1;
	*nodeState(nodeDESKTOP,nodeSPRITE) = sprite;

	// Initialise SDL audio and OpenGL video
	videoInit();
	audioInit();

	// Initialise fonts and load test font
	if (TTF_Init() == -1) { logAdd("TTF_Init: %s\n", TTF_GetError()); exit(2); }
	else {
		// load font.ttf at size 16 into font
		test_font = TTF_OpenFont("./times.ttf",40);
		if (!test_font) logAdd("TTF_OpenFont: %s\n", TTF_GetError());
		else logAdd("Fonts successfully initialised and test font loaded");
		}
	}

void ifExit() {
	SDL_FreeWAV(wavBuf);
	SDL_CloseAudio();
	SDL_Quit();
	}


// ----------------------------------------------------------------------------------------- //
// Nodal event functions (events,click)

// per-frame function
void frame() {

	// Hook rendering workflow in to [[[[desktop]]]]
	nodeLoopInsert(nodeDESKTOP,nodeRENDER);
	*nodeState(nodeRENDER,nodeCODE) = &render;

	// Check if any pending events
	if (SDL_PollEvent(&event)) {
		if (event.type == SDL_QUIT) nodeExit(); // Program exit
		if (event.type == SDL_VIDEORESIZE) {    // Resize window or screen
			surfaceW = event.resize.w;
			surfaceH = event.resize.h;
			surface = SDL_SetVideoMode(surfaceW,surfaceH,bpp,flags);
			videoResize();
			}
		if (event.type == SDL_KEYDOWN) {
			SDL_SaveBMP(surface, "./screenie.bmp");
			logAdd("./screenie.bmp saved");
			}
		if (event.type == SDL_MOUSEBUTTONDOWN) {

			// Store the mouse event metrics on a spriteInfo struct
			spriteInfo *mouse = malloc(sizeof(spriteInfo));
			mouse->x = event.button.x-surfaceW/2;
			mouse->y = event.button.y-surfaceH/2;
			mouse->w = mouse->h = 0;

			// Create new click-node and hook in to [[[[desktop]]]]'s [[[[loop]]]]
			n = nodeLoopInsert(nodeDESKTOP,0);
			*nodeState(n,nodeSPRITE) = mouse;
			*nodeState(n,nodeCODE) = &click;
			logAdd("node %d created for mouse click event",n);
			}
		}

	// Update video display
	SDL_Flip(surface);
	SDL_SetClipRect(surface,NULL);
	SDL_FillRect(surface,NULL,0);
	zorder = 0;
	f++;
	}


// Collision workflow
// - &click is in the [[[[loop]]]] of the sprite-node we're checking the bounds for
void click() {

	// Cmp ptr with bounds of the sprite we're currently hooked into the [[[[loop]]]] of
	int outside = 1;
	spriteInfo *sprite = *nodeState(parent,nodeSPRITE);
	if (sprite) {

		// Get position of mouse pointer
		spriteInfo *pointer = *nodeState(this,nodeSPRITE);
		int px = pointer->x;
		int py = pointer->y;

		// Cmp with parent sprite bounds
		int w = sprite->w;
		int h = sprite->h;
		int x = sprite->x;
		int y = sprite->y;
		outside = (px<x-w/2 || x+w/2<px || py<y-h/2 || y+h/2<py);
		logAdd("(%d,%d) : (%d,%d,%d,%d)",px,py,x-w/2,y-h/2,w,h);
		}

	// Move this workflow depending on inside/outside result
	if (outside) {
		// Pointer is outside parent bounds, pass this workflow to parent's next sibling
		nodeLoopRemove(parent,this);
		if (nodeGetValue(grandpa,nodeLAST) == parent) { // no next sibling, so grandpa is recipient
			nodeLoopInsert(grandpa,this);
			*nodeState(this,nodeCODE) = &spriteInit;
			}
		else nodeLoopInsert(nodeGetValue(parent,nodeNEXT),this);
		}
	else {
		// Pointer inside, pass to first child if exists, if no children [[[[this]]]] is recipient
		if (n = nodeGetValue(parent,nodeFIRST)) {
			nodeLoopRemove(parent,this);
			nodeLoopInsert(n,this);
			node x=nodeGetValue(n,0);
logAdd("    hooked %d into %d (%d<-%d->%d)",this,n,nodeGetValue(x,nodePREV),x,nodeGetValue(x,nodeNEXT));
			}
		else *nodeState(this,nodeCODE) = &spriteInit;
		}
	}


// Draw current sprite
// - this is the rendering workflow which is passed around to complete a frame render
// - each should update the redraw tree which is then rendered in desktop() because currently every rect is a separate [[[[layer]]]]
void render() {

	spriteInfo *sprite = *nodeState(parent,nodeSPRITE);
	if ((parent != nodeDESKTOP) && nodeGetValue(parent,nodeSPRITE) && sprite->scale) {

		// Calculate new sprite metrics from index and [[[[parent]]]] metrics
		spriteInfo *psi = *nodeState(grandpa,nodeSPRITE);
		int wpl = 2;
		int i = sprite->index;
		int w = sprite->w = psi->w/wpl;
		int h = sprite->h = psi->h/wpl;
		int x = sprite->x = i%wpl*w+w/2+psi->x-psi->w/2;
		int y = sprite->y = i/wpl*h+h/2+psi->y-psi->h/2;
		double t = f - sprite->t;
		double s = ((t=t*t)<20000) ? sprite->scale - sin(t/500)*600/t : sprite->scale;
		w = s*w/2;
		h = s*h/2;

		// Fill sprite background (colour based on index currently not sprite->fill)
		int j = zorder++%6+1;
		SDL_Rect bounds = {x-w+surfaceW/2,y-h+surfaceH/2,w*2,h*2};
		SDL_SetClipRect(surface,&bounds);
		SDL_FillRect(surface,NULL,(j&1?0xff:0x80)|(j&2?0xff00:0x8000)|(j&4?0xff0000:0x800000));

		// Draw image if any
		if (sprite->image) {
			int r = 2*w/3;
			SDL_Rect bounds = {x-r+surfaceW/2,y-r+surfaceH/2,r*2,r*2};
			SDL_BlitSurface(sprite->image,NULL,surface,&bounds);
			}

		// Render text if any
		if (sprite->text && sprite->font) {
			SDL_Color color = {j&1?0x80:0xff,j&2?0x80:0xff,j&4?0x80:0xff};
			SDL_Surface *txt;
			txt = TTF_RenderText_Blended(sprite->font,sprite->text,color);
			SDL_Rect bounds = {x-txt->w/2+surfaceW/2,y-txt->h/2+surfaceH/2,txt->w,txt->h};
			SDL_BlitSurface(txt,NULL,surface,&bounds);
			SDL_FreeSurface(txt);
			}
		}

	// Determine next [[[[parent]]]] for this [[[[nodal workflow|workflow]]]] (or 0 if done)
	node p = parent, q = grandpa, n = nodeGetValue(parent,nodeFIRST);
	while ((n == 0) && (q!= nodeSESSION))
		if (nodeGetValue(q,nodeLAST) == p) q = nodeGetValue(p = q,nodePARENT);
		else n = nodeGetValue(p,nodeNEXT);

	// Move render-workflow or call frame() if finished
	nodeLoopRemove(parent,this);
	if (n) nodeLoopInsert(n,this); else frame();
	this = 0;
	}


// ----------------------------------------------------------------------------------------- //
// Nodal sprite functions (INIT,MAIN,EXIT)

// Create new sprite-node and spriteInfo structure from passed mouse-click-coordindates
// - called nodally so that [[[[parent]]]] and [[[[this]]]] are properly set
// - hooked in by the &click workflow once click-recipient resolved
void spriteInit() {

logAdd("spriteInit()");
	// insert new sprite-node into [[[[parent]]]]'s [[[[loop]]]]
	n = nodeLoopInsert(parent,0);

	// Get parent spriteInfo
	spriteInfo *psi = *nodeState(parent,nodeSPRITE);

	// Create new spriteInfo structure
	spriteInfo *sprite = malloc(sizeof(spriteInfo));
	sprite->index = psi->children++; // increment parent's child count
	sprite->children = 0;
	sprite->t = f-50;
	sprite->rotation = 0;
	sprite->scale = 1;
	sprite->image = 0; // test_image removed since plain-vanilla SDL has no scaling or rotation
	sprite->font = test_font;
	sprite->text = "Hello World!";

	// Create the new node and update parent's first and last child info
	*nodeState(n,nodeSPRITE) = sprite;
	if (nodeGetValue(parent,nodeFIRST)==0) nodeSetValue(parent,nodeFIRST,n);
	nodeSetValue(parent,nodeLAST,n);

	// Play the test wav sample
	wavCtr = wavLen;
	wavPtr = wavBuf;
	SDL_PauseAudio(0);

	// Remove the click-event node now that its been serviced (can use spriteExit since we used a spriteInfo struct)
	spriteExit();
	logAdd("New sprite (%d/%d/%d) created, click-event (%d) removed.",grandpa,parent,n,this);
	this = 0;
	}


// Remove the sprite represented by [[[[this]]]] and free its spriteInfo structure
void spriteExit() {
	spriteInfo *sprite = *nodeState(nodeLoopRemove(parent,this),nodeSPRITE);
	if (sprite) free(sprite);
	}


// ----------------------------------------------------------------------------------------- //
// Audio (Init, Main)

// Load test audio wav - see [[[[http://www.libsdl.org/cgi/docwiki.cgi/SDL_5fOpenAudio|SDL_OpenAudio]]]], [[[[http://www.libsdl.org/cgi/docwiki.cgi/SDL_5fLoadWAV|SDL_LoadWav]]]]
void audioInit() {
	wav = malloc(sizeof(SDL_AudioSpec));
	wav->freq = 11025;
	wav->format = AUDIO_U8;
	wav->channels = 1;
	wav->callback = &audioMain;
	wav->samples = 512;
	wav->userdata = NULL;
	SDL_AudioSpec *obtained = malloc(sizeof(SDL_AudioSpec));
	SDL_OpenAudio(wav, obtained);
	if (obtained) wav = obtained;
	if (SDL_LoadWAV("./test.wav",wav,&wavBuf,&wavLen)==NULL) {
		int i=wavLen=2000;
		wavBuf = malloc(wavLen);
		while (i--) wavBuf[i] = i%0xff;
		}
	logAdd("wav->samples = %d",wav->samples);
	}


// Callback for audio playing
void audioMain(void *udata,Uint8 *stream,int len) {
	if (wavCtr == 0) return; // exit if no data left to play
	if (len > wavCtr) len = wavCtr;
	wavCtr -= len;
	while(len--) *stream++ = *wavPtr++;
	}


// ----------------------------------------------------------------------------------------- //
// Video (Init, LoadImage, Resize)

// Set up main video surface & window
void videoInit() {
	if ((SDL_Init(SDL_INIT_VIDEO)<0)) {
		putenv("SDL_VIDEODRIVER=dummy"); // fallback to dummy driver if video won't init
		if ((SDL_Init(SDL_INIT_VIDEO)<0)) logAdd("SDL initialisation failed! (%s)",SDL_GetError());
		}

	const SDL_VideoInfo* video;
	if ((video=SDL_GetVideoInfo())<0) logAdd("getVideoInfo() failed returning \"%s\"",SDL_GetError());
	else bpp = video->vfmt->BitsPerPixel;

	surface = SDL_SetVideoMode(surfaceW,surfaceH,bpp,flags);
	if (surface == NULL) logAdd("setVideoMode() failed returning \"%s\"",SDL_GetError());
	if (errno == 0) logAdd("SDL successfully initialized.");
	SDL_WM_SetCaption(peer,peer);
	videoResize();
	test_image = videoLoadImage("./wheel.png");
	}


// Loads image from passed filename and returns it as a surface
SDL_Surface *videoLoadImage(char *file) {
	SDL_Surface *img = IMG_Load(file);
	int Bpp = img->format->BytesPerPixel;
	if (img) logAdd("Image \"%s\" loaded (%dx%d@%dbpp)",file,img->w,img->h,Bpp*8);
	else logAdd("videoLoadImage(): IMG_Load failed! (%s)",IMG_GetError());
	return img;
	}


// Adjust OpenGL setup on resize
void videoResize() {
	spriteInfo *sprite = *nodeState(nodeDESKTOP,nodeSPRITE);
	sprite->x = 0;
	sprite->y = 0;
	sprite->w = surfaceW;
	sprite->h = surfaceH;
	}