#include "log.h" #include "pause.h" #include "opengl.h" #include "toolkit.h" typedef enum WidgetType_ { WIDGET_NULL, WIDGET_BUTTON, WIDGET_TEXT, WIDGET_IMAGE, WIDGET_LIST } WidgetType; typedef enum WidgetStatus_ { WIDGET_STATUS_NORMAL, WIDGET_STATUS_MOUSEOVER, WIDGET_STATUS_MOUSEDOWN, } WidgetStatus; typedef struct Widget_ { char* name; // Widget name. WidgetType type; // type.. double x,y; // Position. double w,h; // Dimensions. WidgetStatus status; union { // Widget button. struct { void(*fptr) (char*); // Callback. char* display; // Stored text. } btn; // Widget text. struct { char* text; // Use printMid for centered printText if not. glFont* font; glColour* colour; int centered; } txt; struct { // Widget image. glTexture* image; } img; struct { // Widget list. char** options; // Pointer to the options. int noptions; // total number of options. int selected; // Currently selected option. } lst; } dat; } Widget; typedef struct Window_ { unsigned int id; // Unique identifier. char* name; double x,y; // Position. double w,h; // Dimensions. Widget* widgets; // Widget storage. int nwidgets; // Total number of widgets. } Window; static unsigned int genwid = 0; // Generate unique id. int toolkit = 0; #define MIN_WINDOWS 3 static Window* windows = NULL; static int nwindows = 0; static int mwindows = 0; static Widget* window_newWidget(Window* w); static void widget_cleanup(Widget* widget); static Window* window_wget(const unsigned int wid); // Render. static void window_render(Window* w); static void toolkit_renderButton(Widget* btn, double bx, double by); static void toolkit_renderText(Widget* txt, double bx, double by); static void toolkit_renderImage(Widget* img, double bx, double by); // Add a button that when pressed will trigger call, passing it's name as the // only parameter. void window_addButton(const unsigned int wid, const int x, const int y, const int w, const int h, char* name, char* display, void (*call)(char*)) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_BUTTON; wgt->name = strdup(name); wgt->dat.btn.display = strdup(display); // Set the properties. wgt->w = (double) w; wgt->h = (double) h; if(x < 0) wgt->x = wdw->w - wgt->w + x; else wgt->x = (double)x; if(y < 0) wgt->y = wdw->h - wgt->h + y; else wgt->y = (double)y; wgt->dat.btn.fptr = call; } // Add text to the window. void window_addText(const unsigned int wid, const int x, const int y, const int w, const int h, const int centered, char* name, glFont* font, glColour* colour, char* string) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_TEXT; wgt->name = strdup(name); // Display the widgets name. // Set the properties. wgt->w = (double) w; wgt->h = (double) h; if(font == NULL) wgt->dat.txt.font = &gl_defFont; else wgt->dat.txt.font = font; if(x < 0) wgt->x = wdw->w - wgt->w + x; else wgt->x = (double)x; if(y < 0) wgt->y = wdw->h + y; else wgt->y = (double) y; wgt->dat.txt.colour = colour; wgt->dat.txt.centered = centered; wgt->dat.txt.text = strdup(string); } // Add a graphic to the window. void window_addImage(const unsigned int wid, const int x, const int y, char* name, glTexture* image) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_IMAGE; wgt->name = strdup(name); // Set the propertied. wgt->dat.img.image = image; if(x < 0) wgt->x = wdw->w - wgt->dat.img.image->sw + x; else wgt->x = (double)x; if(y < 0) wgt->y = wdw->h + y; else wgt->y = (double)y; } void window_addList(const unsigned int wid, const int x, const int y, const int w, const int h, char* name, char** items, int nitems, int defitem) { Window *wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_LIST; wgt->name = strdup(name); wgt->dat.lst.options = items; wgt->dat.lst.noptions = nitems; wgt->dat.lst.selected = defitem; // -1 would be none. wgt->w = (double) w; wgt->h = (double) h; if(x < 0) wgt->x = wdw->w - wgt->w + x; else wgt->x = (double) x; if(y < 0) wgt->y = wdw->h - wgt->h + y; else wgt->y = (double) y; } // Return pointer to newly allocated widget. static Widget* window_newWidget(Window* w) { Widget* wgt = NULL; w->widgets = realloc(w->widgets, sizeof(Widget)*(++w->nwidgets)); if(w->widgets == NULL) WARN("Out of memory"); wgt = &w->widgets[w->nwidgets - 1]; wgt->type = WIDGET_NULL; wgt->status = WIDGET_STATUS_NORMAL; return wgt; } // Return the window of id wid. static Window* window_wget(const unsigned int wid) { int i; for(i = 0; i < nwindows; i++) if(windows[i].id == wid) return &windows[i]; DEBUG("Window '%d' not found in windows stack", wid); return NULL; } // Check if a window exists. int window_exists(const char* wdwname) { int i; for(i = 0; i < nwindows; i++) if(strcmp(windows[i].name, wdwname)==0) return 1; // Exists. return 0; // Does not exits! } // Return the id of a window. unsigned int window_get(const char* wdwname) { int i; for(i = 0; i < nwindows; i++) if(strcmp(windows[i].name, wdwname)==0) return windows[i].id; DEBUG("Window '%s' not found in windows stack", wdwname); return 0; } // Create a window. unsigned int window_create(char* name, const int x, const int y, const int w, const int h) { if(nwindows >= mwindows) { // We have reached our memory limit. windows = realloc(windows, sizeof(Window)*(++mwindows)); if(windows == NULL) WARN("Out of memory"); } const int wid = (++genwid); // Unique id windows[nwindows].id = wid; windows[nwindows].name = strdup(name); windows[nwindows].w = (double) w; windows[nwindows].h = (double) h; if((x == -1) && (y == -1)) { // Center. windows[nwindows].x = gl_screen.w/2. - windows[nwindows].w/2.; windows[nwindows].y = gl_screen.h/2. - windows[nwindows].h/2.; } else { windows[nwindows].x = (double) x; windows[nwindows].y = (double) y; } windows[nwindows].widgets = NULL; windows[nwindows].nwidgets = 0; nwindows++; if(toolkit == 0) { // Toolkit is enabled. SDL_ShowCursor(SDL_ENABLE); toolkit = 1; // Enable it. } return wid; } // Destroy a widget. static void widget_cleanup(Widget* widget) { if(widget->name) free(widget->name); switch(widget->type) { case WIDGET_BUTTON: if(widget->dat.btn.display) free(widget->dat.btn.display); break; case WIDGET_TEXT: if(widget->dat.txt.text) free(widget->dat.txt.text); break; default: break; } } // Destroy a window. void window_destroy(const unsigned int wid) { int i, j; // Destroy the window. for(i = 0; i < nwindows; i++) if(windows[i].id == wid) { if(windows[i].name) free(windows[i].name); for(j = 0; j < windows[i].nwidgets; j++) widget_cleanup(&windows[i].widgets[j]); free(windows[i].widgets); break; } // Move the other windows down a layer. for(; i<(nwindows-1); i++) windows[i] = windows[i+1]; nwindows--; if(nwindows == 0) { // No windows left. SDL_ShowCursor(SDL_DISABLE); toolkit = 0; // Disable the toolkit. if(paused) unpause(); } } void window_destroyWidget(unsigned int wid, const char* wgtname) { Window* w = window_wget(wid); int i; for(i = 0; i < w->nwidgets; i++) if(strcmp(wgtname, w->widgets[i].name)==0) break; if(i >= w->nwidgets) { DEBUG("Widget '%s' not found in window '%d'", wgtname, wid); return; } widget_cleanup(&w->widgets[i]); if(i < w->nwidgets-1) // This isn't the last widget. w->widgets[i] = w->widgets[i-1]; w->nwidgets--; // Not that we don't actually realloc the space.. } // Render a window. static void window_render(Window* w) { int i; double x, y; glColour *lc, *c, *dc, *oc; // Position. x = w->x - (double)gl_screen.w/2.; y = w->y - (double)gl_screen.h/2.; // Colours. lc = &cGrey90; c = &cGrey70; dc = &cGrey50; oc = &cGrey30; // Window shaded background. // Main body. glShadeModel(GL_SMOOTH); glBegin(GL_QUADS); COLOUR(*dc); glVertex2d(x+21., y); glVertex2d(x+w->w-21., y); COLOUR(*c); glVertex2d(x+w->w-21, y+0.6*w->h); glVertex2d(x+21, y+0.6*w->h); glEnd(); glShadeModel(GL_FLAT); glBegin(GL_QUADS); COLOUR(*c); glVertex2d(x+21., y+0.6*w->h); glVertex2d(x+w->w-21., y+0.6*w->h); glVertex2d(x+w->w-21., y+w->h); glVertex2d(x+21., y+w->h); glEnd(); glShadeModel(GL_SMOOTH); // Left side. glBegin(GL_POLYGON); COLOUR(*c); glVertex2d(x+21., y+0.6*w->h); // Center. COLOUR(*dc); glVertex2d(x+21., y); glVertex2d(x+15., y+1.); glVertex2d(x+10., y+3.); glVertex2d(x+6., y+6.); glVertex2d(x+3., y+10.); glVertex2d(x+1., y+15.); glVertex2s(x, y+21.); COLOUR(*c); glVertex2d(x, y+0.6*w->h); // Front of center. glVertex2d(x, y+w->h-21.); glVertex2d(x+1., y+w->h-15.); glVertex2d(x+3., y+w->h-10.); glVertex2d(x+6., y+w->h-6.); glVertex2d(x+10., y+w->h-3.); glVertex2d(x+15., y+w->h-1.); glVertex2d(x+21., y+w->h); glEnd(); // Right side. glBegin(GL_POLYGON); COLOUR(*c); glVertex2d(x+w->w-21., y+0.6*w->h); // Center. COLOUR(*dc); glVertex2d(x+w->w-21., y); glVertex2d(x+w->w-15., y+1.); glVertex2d(x+w->w-10., y+3.); glVertex2d(x+w->w-6., y+6.); glVertex2d(x+w->w-3., y+10.); glVertex2d(x+w->w-1., y+15.); glVertex2d(x+w->w, y+21.); COLOUR(*c); glVertex2d(x+w->w, y+0.6*w->h); // Front of center. glVertex2d(x+w->w, y+w->h-21.); glVertex2d(x+w->w-1., y+w->h-15.); glVertex2d(x+w->w-3., y+w->h-10.); glVertex2d(x+w->w-6., y+w->h-6.); glVertex2d(x+w->w-10., y+w->h-3.); glVertex2d(x+w->w-15., y+w->h-1.); glVertex2d(x+w->w-21., y+w->h); glEnd(); // Inner outline. glShadeModel(GL_SMOOTH); glBegin(GL_LINE_LOOP); // Left side. COLOUR(*c); glVertex2d(x+21.+1., y); glVertex2d(x+15.+1., y+1.+1.); glVertex2d(x+10.+1., y+3.+1.); glVertex2d(x+6.+1., y+6.+1.); glVertex2d(x+3.+1., y+10.+1.); glVertex2d(x+1.+1., y+15.+1.); glVertex2d(x+1., y+21.+1.); COLOUR(*lc); glVertex2d(x+1., y+0.6*w->h); // Front of center. glVertex2d(x+1., y+w->h-21.-1.); glVertex2d(x+1.+1., y+w->h-15.-1.); glVertex2d(x+3.+1., y+w->h-10.-1.); glVertex2d(x+6.+1., y+w->h-6.-1.); glVertex2d(x+10.+1., y+w->h-2.-1.); glVertex2d(x+15.+1., y+w->h-1.-1.); glVertex2d(x+21.+1., y+w->h-1.); // Switch to right via top. glVertex2d(x+w->w-21.-1., y+w->h); glVertex2d(x+w->w-15.-1., y+w->h-1.-1.); glVertex2d(x+w->w-10.-1., y+w->h-3.-1.); glVertex2d(x+w->w-6.-1., y+w->h-6.-1.); glVertex2d(x+w->w-2.-1., y+w->h-10.-1.); glVertex2d(x+w->w-1.-1., y+w->h-15.-1.); glVertex2d(x+w->w-.1, y+w->h-21.-1.); glVertex2d(x+w->w-1., y+0.6*w->h); // Front of center. COLOUR(*c); glVertex2d(x+w->w-1., y+21.+1.); glVertex2d(x+w->w-1.-1., y+15.+1.); glVertex2d(x+w->w-3.-1., y+10.+1.); glVertex2d(x+w->w-6.-1., y+6.+1.); glVertex2d(x+w->w-10.-1., y+3.+1.); glVertex2d(x+w->w-15.-1., y+1.+1.); glVertex2d(x+w->w-21.-1., y+1.); glVertex2d(x+21.+1., y+1.); glEnd(); // Outter outline. glShadeModel(GL_FLAT); glBegin(GL_LINE_LOOP); // Left side. COLOUR(*oc); glVertex2d(x+21., y); glVertex2d(x+15., y+1.); glVertex2d(x+10., y+3.); glVertex2d(x+6., y+6.); glVertex2d(x+3., y+10.); glVertex2d(x+1., y+15.); glVertex2d(x, y+21.); glVertex2d(x, y+0.6*w->h); // Front of center. glVertex2d(x, y+w->h-21.); glVertex2d(x+1., y+w->h-15.); glVertex2d(x+3., y+w->h-10.); glVertex2d(x+6., y+w->h-6.); glVertex2d(x+10., y+w->h-3.); glVertex2d(x+15., y+w->h-1.); glVertex2d(x+21., y+w->h); // Switch to right via top. glVertex2d(x+w->w-21., y+w->h); glVertex2d(x+w->w-15., y+w->h-1.); glVertex2d(x+w->w-10., y+w->h-3.); glVertex2d(x+w->w-6., y+w->h-6.); glVertex2d(x+w->w-3., y+w->h-10.); glVertex2d(x+w->w-1., y+w->h-15.); glVertex2d(x+w->w, y+w->h-21.); glVertex2d(x+w->w, y+0.6*w->h); // Front of center. glVertex2d(x+w->w, y+21.); glVertex2d(x+w->w-1., y+15.); glVertex2d(x+w->w-3., y+10.); glVertex2d(x+w->w-6., y+6.); glVertex2d(x+w->w-10., y+3.); glVertex2d(x+w->w-15., y+1.); glVertex2d(x+w->w-21., y); glVertex2d(x+21., y); // Back to beginning. glEnd(); // Render the window name. gl_printMid(&gl_defFont, w->w, x + (double)gl_screen.w/2., y + w->h - 20. + (double)gl_screen.h/2., &cBlack, w->name); // Widgets. for(i = 0; i < w->nwidgets; i++) { switch(w->widgets[i].type) { case WIDGET_NULL: break; case WIDGET_BUTTON: toolkit_renderButton(&w->widgets[i], x, y); break; case WIDGET_TEXT: toolkit_renderText(&w->widgets[i], x, y); break; case WIDGET_IMAGE: toolkit_renderImage(&w->widgets[i], x, y); break; case WIDGET_LIST: // TODO widget list rendering. break; } } } static void toolkit_renderButton(Widget* btn, double bx, double by) { glColour* c, *dc, *oc, *lc; double x, y; x = bx + btn->x; y = by + btn->y; switch(btn->status) { // Set the color. case WIDGET_STATUS_NORMAL: lc = &cGrey80; c = &cGrey60; dc = &cGrey40; oc = &cGrey20; break; case WIDGET_STATUS_MOUSEOVER: lc = &cWhite; c = &cGrey80; dc = &cGrey60; oc = &cGrey40; break; case WIDGET_STATUS_MOUSEDOWN: lc = &cGreen; c = &cGreen; dc = &cGrey40; oc = &cGrey20; break; } // Shaded base. glShadeModel(GL_SMOOTH); glBegin(GL_QUADS); COLOUR(*dc); glVertex2d(x, y+2/3*btn->h); glVertex2d(x+btn->w, y+2/3*btn->h); COLOUR(*c); glVertex2d(x+btn->w, y+0.6*btn->h); glVertex2d(x, y+0.6*btn->h); glEnd(); glShadeModel(GL_FLAT); glBegin(GL_QUADS); COLOUR(*c); glVertex2d(x, y+0.6*btn->h); glVertex2d(x+btn->w, y+0.6*btn->h); glVertex2d(x+btn->w, y+btn->h); glVertex2d(x, y+btn->h); glEnd(); // Inner outline. glShadeModel(GL_SMOOTH); glBegin(GL_LINE_LOOP); // Left. COLOUR(*c); glVertex2d(x, y); COLOUR(*lc); glVertex2d(x, y+btn->h); // Top. glVertex2d(x+btn->w, y+btn->h); // Right. COLOUR(*c); glVertex2d(x+btn->w, y); // Bottom. glVertex2d(x, y); glEnd(); // Outter outline. glShadeModel(GL_FLAT); glBegin(GL_LINE_LOOP); COLOUR(cBlack); // Left. glVertex2d(x-1., y); glVertex2d(x-1., y+btn->h); // Top. glVertex2d(x, y+btn->h+1.); glVertex2d(x+btn->w, y+btn->h+1.); // Right. glVertex2d(x+btn->w+1., y+btn->h); glVertex2d(x+btn->w+1., y); // Bottom. glVertex2d(x+btn->w, y-1.); glVertex2d(x, y-1.); glVertex2d(x-1, y); glEnd(); gl_printMid(NULL, (int)btn->w, bx + (double)gl_screen.w/2. + btn->x, by + (double)gl_screen.h/2. + btn->y + (btn->h - gl_defFont.h)/2., &cDarkRed, btn->dat.btn.display); } static void toolkit_renderText(Widget* txt, double bx, double by) { if(txt->dat.txt.centered) gl_printMid(txt->dat.txt.font, txt->w, bx + (double)gl_screen.w/2. + txt->x, by + (double)gl_screen.h/2. + txt->y, txt->dat.txt.colour, txt->dat.txt.text); else gl_printText(txt->dat.txt.font, txt->w, txt->h, bx + (double)gl_screen.w/2. + txt->x, by + (double)gl_screen.h/2. + txt->y, txt->dat.txt.colour, txt->dat.txt.text); } // Render the image. static void toolkit_renderImage(Widget* img, double bx, double by) { glColour* lc, *c, *oc; double x, y; x = bx + img->x; y = by + img->y; lc = &cGrey90; c = &cGrey70; oc = &cGrey30; // Image. gl_blitStatic(img->dat.img.image, x + (double)gl_screen.w/2., y + (double)gl_screen.h/2., NULL); // Inner outline (outwards). glShadeModel(GL_SMOOTH); glBegin(GL_LINE_LOOP); COLOUR(*lc); // Top. glVertex2d(x-1, y+img->dat.img.image->sh+1.); glVertex2d(x+img->dat.img.image->sw, y+img->dat.img.image->sh+1.); // Right. COLOUR(*c); glVertex2d(x+img->dat.img.image->sw, y); // Bottom. glVertex2d(x-1., y); // Left. COLOUR(*lc); glVertex2d(x-1., y+img->dat.img.image->sh+1.); glEnd(); // Outter outline. glShadeModel(GL_SMOOTH); glBegin(GL_LINE_LOOP); COLOUR(*oc); // Top. glVertex2d(x-2., y+img->dat.img.image->sh+2.); glVertex2d(x+img->dat.img.image->sw+1., y+img->dat.img.image->sh+2.); // Right. glVertex2d(x+img->dat.img.image->sw+1., y-1.); // Bottom. glVertex2d(x-2., y-1.); // Left. glVertex2d(x-2., y+img->dat.img.image->sh+2.); glEnd(); } // Render the window. void toolkit_render(void) { int i; if(gl_has(OPENGL_AA_LINE)) glEnable(GL_LINE_SMOOTH); if(gl_has(OPENGL_AA_POLYGON)) glEnable(GL_POLYGON_SMOOTH); for(i = 0; i < nwindows; i++) window_render(&windows[i]); if(gl_has(OPENGL_AA_LINE)) glDisable(GL_LINE_SMOOTH); if(gl_has(OPENGL_AA_POLYGON)) glDisable(GL_POLYGON_SMOOTH); } // Input. static int mouse_down = 0; void toolkit_mouseEvent(SDL_Event* event) { int i; double x, y; Window* w; Widget* wgt; // Set mouse button status. if(event->type == SDL_MOUSEBUTTONDOWN) mouse_down = 1; else if(event->type == SDL_MOUSEBUTTONUP) mouse_down = 0; // Ignore movements if mouse is down. else if((event->type == SDL_MOUSEMOTION) && mouse_down) return; // Absolute positions. if(event->type == SDL_MOUSEMOTION) { x = (double)event->motion.x; y = gl_screen.h - (double)event->motion.y; } else if((event->type == SDL_MOUSEBUTTONDOWN) || (event->type == SDL_MOUSEBUTTONUP)) { x = (double)event->button.x; y = gl_screen.h - (double)event->motion.y; } w = &windows[nwindows-1]; if((x < w->x) || (x > (w->x + w->w)) || (y < w->y) || (y > (w->y + w->h))) return; // Not in current window. // Relative positions. x -= w->x; y -= w->y; for(i = 0; i < w->nwidgets; i++) { wgt = &w->widgets[i]; if((x > wgt->x) && (x < (wgt->x + wgt->w)) && (y > wgt->y) && (y < (wgt->y + wgt->h))) { switch(event->type) { case SDL_MOUSEMOTION: wgt->status = WIDGET_STATUS_MOUSEOVER; break; case SDL_MOUSEBUTTONDOWN: wgt->status = WIDGET_STATUS_MOUSEDOWN; break; case SDL_MOUSEBUTTONUP: if(wgt->status == WIDGET_STATUS_MOUSEDOWN) { if(wgt->type == WIDGET_BUTTON) (*wgt->dat.btn.fptr)(wgt->name); } wgt->status = WIDGET_STATUS_NORMAL; break; } } else wgt->status = WIDGET_STATUS_NORMAL; } } // Init. int toolkit_init(void) { windows = malloc(sizeof(Window)*MIN_WINDOWS); nwindows = 0; mwindows = MIN_WINDOWS; SDL_ShowCursor(SDL_DISABLE); return 0; } // Exit the toolkit. void toolkit_exit(void) { int i; for(i = 0; i < nwindows; i++) { window_destroy(windows[i].id); free(windows); } }