Interface.c
From Organic Design wiki
// [[[[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;
}