#include "lephisto.h" #include "log.h" #include "pause.h" #include "opengl.h" #include "input.h" #include "toolkit.h" #define INPUT_DELAY 500 #define INPUT_FREQ 100 typedef enum WidgetType_ { WIDGET_NULL, WIDGET_BUTTON, WIDGET_TEXT, WIDGET_IMAGE, WIDGET_LIST, WIDGET_RECT, WIDGET_CUST, WIDGET_INPUT } 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*); // Active callback. char* display; // Stored text. int disabled; } 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; glColour* colour; int border; } img; struct { // Widget list. char** options; // Pointer to the options. int noptions; // total number of options. int selected; // Currently selected option. int pos; // Current topmost option (in view). void (*fptr) (char*); // Modify callback. } lst; // Widget rect. struct { glColour* colour; // Background colour. int border; // Border. } rct; // Widget cust. struct { int border; void(*render) (double bx, double by, double bw, double bh); void(*mouse) (SDL_Event* event, double bx, double by); } cst; struct { // Widget input. char* input; // Input buffer. int max; // Max length. int oneline; // Is it a one-line? '\n' and co. if no. int view, pos; // View and cursor position. } inp; } dat; } Widget; typedef struct Window_ { unsigned int id; // Unique identifier. char* name; int hidden; // Is it hidden? int focus; // Which window is focused. // Pointer to a function to run if user hits 'enter' and no button is focused // nor any other input thiny that catches 'enter'. void(*def_fptr)(char*); 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 > 0. int toolkit = 0; #define MIN_WINDOWS 3 static Window* windows = NULL; static int nwindows = 0; static int mwindows = 0; // Default outline colours. static glColour* toolkit_colLight = &cGrey90; static glColour* toolkit_col = &cGrey70; static glColour* toolkit_colDark = &cGrey30; // Extern. extern void main_loop(void); // lephisto.c // Static. static Widget* window_newWidget(Window* w); static void widget_cleanup(Widget* widget); static Window* window_wget(const unsigned int wid); static Widget* window_getwgt(const unsigned int wid, char* name); // Input. static int toolkit_inputInput(Uint8 type, Widget* inp, SDLKey key); static void toolkit_mouseEvent(SDL_Event* event); static int toolkit_keyEvent(SDL_Event* event); // Focus. static void toolkit_nextFocus(void); static int toolkit_isFocusable(Widget* wgt); static void toolkit_triggerFocus(void); static Widget* toolkit_getFocus(void); static void toolkit_listScroll(Widget* wgt, int direction); static void toolkit_listFocus(Widget* lst, double bx, double by); // 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); static void toolkit_renderList(Widget* lst, double bx, double by); static void toolkit_renderRect(Widget* rct, double bx, double by); static void toolkit_renderCust(Widget* cst, double bx, double by); static void toolkit_renderInput(Widget* inp, double bx, double by); static void toolkit_drawOutline(double x, double y, double w, double h, double b, glColour* c, glColour* lc); static void toolkit_clip(double x, double y, double w, double h); static void toolkit_unclip(void); static void toolkit_drawRect(double x, double y, double w, double h, glColour* c, glColour* lc); // Dialogues.. static glFont* dialogue_getSize(char* msg, int* w, int* h); static void dialogue_alertClose(char* str); static void dialogue_msgClose(char* str); static void dialogue_YesNoClose(char* str); static void dialogue_inputClose(char* str); // Secondary loop hack. static int loop_done; static int toolkit_loop(void); // 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); // Specific. wgt->type = WIDGET_BUTTON; wgt->name = strdup(name); wgt->dat.btn.display = strdup(display); wgt->dat.btn.disabled = 0; // Initially enabled. // 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; if(wdw->focus == -1) // Init the focus. toolkit_nextFocus(); } // 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 - h; else wgt->y = (double) y; if(colour == NULL) wgt->dat.txt.colour = &cBlack; else wgt->dat.txt.colour = colour; wgt->dat.txt.centered = centered; if(string) wgt->dat.txt.text = strdup(string); else wgt->dat.txt.text = NULL; } // Add a graphic to the window. void window_addImage(const unsigned int wid, const int x, const int y, char* name, glTexture* image, int border) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_IMAGE; wgt->name = strdup(name); // Set the properties. wgt->dat.img.image = image; wgt->dat.img.border = border; wgt->dat.img.colour = NULL; // Normal colour. wgt->w = (image == NULL) ? 0 : wgt->dat.img.image->sw; wgt->h = (image == NULL) ? 0 : wgt->dat.img.image->sh; 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; } 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, void(*call) (char*)) { 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->dat.lst.pos = 0; wgt->dat.lst.fptr = call; wgt->w = (double) w; wgt->h = (double) h - ((h % (gl_defFont.h+2)) + 2); 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; if(wdw->focus == -1) // Initialize the focus. toolkit_nextFocus(); } void window_addRect(const unsigned int wid, const int x, const int y, const int w, const int h, char* name, glColour* colour, int border) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); wgt->type = WIDGET_RECT; wgt->name = strdup(name); wgt->dat.rct.colour = colour; wgt->dat.rct.border = border; 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; } void window_addCust(const unsigned int wid, const int x, const int y, const int w, const int h, char* name, const int border, void(*render) (double x, double y, double w, double h), void(*mouse) (SDL_Event* event, double x, double y)) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); // Generic. wgt->type = WIDGET_CUST; wgt->name = strdup(name); // Specific. wgt->dat.cst.border = border; wgt->dat.cst.render = render; wgt->dat.cst.mouse = mouse; // Position/size. 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; } // Add an input widget. void window_addInput(const unsigned int wid, const int x, const int y, const int w, const int h, char* name, const int max, const int oneline) { Window* wdw = window_wget(wid); Widget* wgt = window_newWidget(wdw); // Generic. wgt->type = WIDGET_INPUT; wgt->name = strdup(name); // Specific. wgt->dat.inp.max = max+1; wgt->dat.inp.oneline = oneline; wgt->dat.inp.pos = 0; wgt->dat.inp.view = 0; wgt->dat.inp.input = malloc(sizeof(char)*wgt->dat.inp.max); memset(wgt->dat.inp.input, 0, wgt->dat.inp.max*sizeof(char)); // Position/Size. 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->nwidgets++; 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; } static Widget* window_getwgt(const unsigned int wid, char* name) { int i; Window* wdw = window_wget(wid); for(i = 0; i < wdw->nwidgets; i++) if(strcmp(wdw->widgets[i].name, name)==0) return &wdw->widgets[i]; return NULL; } void window_modifyText(const unsigned int wid, char* name, char* newstring) { Widget* wgt = window_getwgt(wid, name); if(wgt->dat.txt.text) free(wgt->dat.txt.text); wgt->dat.txt.text = (newstring) ? strdup(newstring) : NULL; } // Disable a button. void window_disableButton(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); if(wgt->type != WIDGET_BUTTON) { DEBUG("Trying to disable a non-button widget '%s'", name); return; } wgt->dat.btn.disabled = 1; } // Enable a button. void window_enableButton(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); if(wgt->type != WIDGET_BUTTON) { DEBUG("Trying to enable a non-button widget '%s'", name); return; } wgt->dat.btn.disabled = 0; } void window_modifyImage(const unsigned int wid, char* name, glTexture* image) { Widget* wgt = window_getwgt(wid, name); wgt->dat.img.image = image; } void window_imgColour(const unsigned int wid, char* name, glColour* colour) { Widget* wgt = window_getwgt(wid, name); wgt->dat.img.colour = colour; } glTexture* window_getImage(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); return (wgt) ? wgt->dat.img.image : NULL; } // Get the input from an input widget. char* window_getInput(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); return(wgt) ? wgt->dat.inp.input : 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) { Window* wdw; 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 wdw = &windows[nwindows]; wdw->id = wid; wdw->name = strdup(name); wdw->hidden = 0; wdw->focus = -1; wdw->def_fptr = NULL; wdw->w = (double)w; wdw->h = (double)h; // x pos. if(x == -1) // Center. wdw->x = gl_screen.w/2. - windows[nwindows].w/2.; else if(x < 0) wdw->x = gl_screen.w - windows[nwindows].w + (double)x; else windows[nwindows].x = (double)x; // y pos. if(y == -1) // Center. wdw->y = gl_screen.h/2. - windows[nwindows].h/2.; else if(y < 0) wdw->x = gl_screen.h - windows[nwindows].h + (double)y; else wdw->y = (double)y; wdw->widgets = NULL; wdw->nwidgets = 0; nwindows++; if(toolkit == 0) { // Toolkit is enabled. SDL_ShowCursor(SDL_ENABLE); toolkit = 1; // Enable it. pause_game(); } return wid; } // Sets the window's default function. void window_setFptr(const unsigned int wid, void(*fptr)(char*)) { Window* wdw; wdw = window_wget(wid); if(wdw != NULL) wdw->def_fptr = fptr; } // Destroy a widget. static void widget_cleanup(Widget* widget) { int i; if(widget->name) free(widget->name); switch(widget->type) { case WIDGET_BUTTON: // Must clear the button display text. if(widget->dat.btn.display) free(widget->dat.btn.display); break; case WIDGET_TEXT: // Must clear the text. if(widget->dat.txt.text) free(widget->dat.txt.text); break; case WIDGET_LIST: // Must clear the list. if(widget->dat.lst.options) { for(i = 0; i < widget->dat.lst.noptions; i++) if(widget->dat.lst.options[i]) free(widget->dat.lst.options[i]); free(widget->dat.lst.options); } break; case WIDGET_INPUT: free(widget->dat.inp.input); // Free the input buffer. 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_game(); } } void window_destroyWidget(unsigned int wid, const char* wgtname) { Window* w = window_wget(wid); int i; if(w == NULL) { WARN("Window %d does not exist", wid); return; } 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; } if(w->focus == i) w->focus = -1; widget_cleanup(&w->widgets[i]); if(i < w->nwidgets-1) // This isn't the last widget. memmove(&w->widgets[i], &w->widgets[i+1], sizeof(Widget) * (w->nwidgets-i-1)); (w->nwidgets)--; // Not that we don't actually realloc the space.. } static void toolkit_drawOutline(double x, double y, double w, double h, double b, glColour* c, glColour* lc) { glShadeModel((lc == NULL) ? GL_FLAT : GL_SMOOTH); if(!lc) COLOUR(*c); glBegin(GL_LINE_LOOP); // Left. if(lc) COLOUR(*lc); glVertex2d(x-b, y); if(lc) COLOUR(*c); glVertex2d(x-b, y+h); // Top. glVertex2d(x, y+h+b); glVertex2d(x+w, y+h+b); // Right. glVertex2d(x+w+b, y+h); if(lc) COLOUR(*lc); glVertex2d(x+w+b, y); // Bottom. glVertex2d(x+w, y-b); glVertex2d(x, y-b); glVertex2d(x-b, y); glEnd(); } static void toolkit_drawRect(double x, double y, double w, double h, glColour* c, glColour* lc) { glShadeModel((lc) ? GL_SMOOTH : GL_FLAT); glBegin(GL_QUADS); COLOUR(*c); glVertex2d(x, y); glVertex2d(x+w, y); COLOUR((lc) ? *lc : *c); glVertex2d(x+w, y+h); glVertex2d(x, y+h); glEnd(); } // Set up 2d clipping planes around a rectangle. static void toolkit_clip(double x, double y, double w, double h) { GLdouble ctop[4] = { 0., 1., 0., -y }; GLdouble cbot[4] = { 0., -1., 0., y+h }; GLdouble clef[4] = { 1., 0., 0., -x }; GLdouble crig[4] = { -1., 0., 0, x+w }; glClipPlane(GL_CLIP_PLANE0, ctop); glClipPlane(GL_CLIP_PLANE1, cbot); glClipPlane(GL_CLIP_PLANE2, clef); glClipPlane(GL_CLIP_PLANE3, crig); glEnable(GL_CLIP_PLANE0); glEnable(GL_CLIP_PLANE1); glEnable(GL_CLIP_PLANE2); glEnable(GL_CLIP_PLANE3); } static void toolkit_unclip(void) { glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); } // Render a window. static void window_render(Window* w) { int i; double x, y, wid, hei; 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. toolkit_drawRect(x+21, y, w->w-42., 0.6*w->h, dc, c); toolkit_drawRect(x+21, y+0.6*w->h, w->w-42., 0.4*w->h, c, NULL); 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: toolkit_renderList(&w->widgets[i], x, y); break; case WIDGET_RECT: toolkit_renderRect(&w->widgets[i], x, y); break; case WIDGET_CUST: toolkit_renderCust(&w->widgets[i], x, y); break; case WIDGET_INPUT: toolkit_renderInput(&w->widgets[i], x, y); break; } } // Focus widget. if(w->focus != -1) { x += w->widgets[w->focus].x; y += w->widgets[w->focus].y; wid = w->widgets[w->focus].w; hei = w->widgets[w->focus].h; toolkit_drawOutline(x, y, wid, hei, 3, &cBlack, NULL); } } // Renders a button. static void toolkit_renderButton(Widget* btn, double bx, double by) { glColour* c, *dc, *lc; double x, y; x = bx + btn->x; y = by + btn->y; if(btn->dat.btn.disabled == 1) { lc = &cGrey60; c = &cGrey20; dc = &cGrey40; } else { switch(btn->status) { // Set the color. case WIDGET_STATUS_NORMAL: lc = &cGrey80; c = &cGrey60; dc = &cGrey40; break; case WIDGET_STATUS_MOUSEOVER: lc = &cWhite; c = &cGrey80; dc = &cGrey60; break; case WIDGET_STATUS_MOUSEDOWN: lc = &cGreen; c = &cGreen; dc = &cGrey40; break; default: break; } } // Shaded base. if(btn->dat.btn.disabled == 1) { toolkit_drawRect(x, y, btn->w, 0.4*btn->h, dc, NULL); toolkit_drawRect(x, y+0.4*btn->h, btn->w, 0.6*btn->h, dc, c); } else { toolkit_drawRect(x, y, btn->w, 0.6*btn->h, dc, c); toolkit_drawRect(x, y+0.6*btn->h, btn->w, 0.4*btn->h, c, NULL); } // Inner outline. toolkit_drawOutline(x, y, btn->w, btn->h, 0., lc, c); // Outter outline. toolkit_drawOutline(x, y, btn->w, btn->h, 1., &cBlack, NULL); 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.text == NULL) return; 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) { double x, y; if(img->dat.img.image == NULL) return; x = bx + img->x; y = by + img->y; // Image. gl_blitStatic(img->dat.img.image, x + (double)gl_screen.w/2., y + (double)gl_screen.h/2., img->dat.img.colour); if(img->dat.img.border) { // Inner outline (outwards). toolkit_drawOutline(x, y+1, img->dat.img.image->sw-1, img->dat.img.image->sh-1, 1., toolkit_colLight, toolkit_col); // Outter outline. toolkit_drawOutline(x, y+1, img->dat.img.image->sw-1, img->dat.img.image->sh-1, 2., toolkit_colDark, NULL); } } // Render the list. static void toolkit_renderList(Widget* lst, double bx, double by) { int i; double x, y, tx, ty; x = bx + lst->x; y = by + lst->y; // List bg. toolkit_drawRect(x, y, lst->w, lst->h, &cWhite, NULL); // Inner outline. toolkit_drawOutline(x, y, lst->w, lst->h, 0., toolkit_colLight, toolkit_col); // Outter outline. toolkit_drawOutline(x, y, lst->w, lst->h, 1., toolkit_colDark, NULL); // Draw selected. toolkit_drawRect(x, y-1.+lst->h-(1+lst->dat.lst.selected-lst->dat.lst.pos)*(gl_defFont.h+2.), lst->w, gl_defFont.h+2., &cHilight, NULL); // Draw content. tx = (double)gl_screen.w/2. + x+2.; ty = (double)gl_screen.h/2. + y+lst->h - 2. - gl_defFont.h; y = ty-2.; for(i = lst->dat.lst.pos; i < lst->dat.lst.noptions; i++) { gl_printMax(&gl_defFont, (int)lst->w-4, tx, ty, &cBlack, lst->dat.lst.options[i]); ty -= 2 + gl_defFont.h; if(ty-y > lst->h) break; } } // Render a rectangle. static void toolkit_renderRect(Widget* rct, double bx, double by) { double x, y; x = bx + rct->x; y = by + rct->y; if(rct->dat.rct.colour) // Draw rect only if it exists. toolkit_drawRect(x, y, rct->w, rct->h, rct->dat.rct.colour, NULL); if(rct->dat.rct.border) { // Inner outline. toolkit_drawOutline(x, y, rct->w, rct->h, 0., toolkit_colLight, toolkit_col); // Outter outline. toolkit_drawOutline(x, y, rct->w, rct->h, 1., toolkit_colDark, NULL); } } // Render a custom widget. static void toolkit_renderCust(Widget* cst, double bx, double by) { double x, y; x = bx + cst->x; y = by + cst->y; if(cst->dat.cst.border) { // Inner outline. toolkit_drawOutline(x-1, y+1, cst->w+1, cst->h+1, 0., toolkit_colLight, toolkit_col); // Outter outline. toolkit_drawOutline(x-1, y, cst->w+1, cst->h+1, 1., toolkit_colDark, NULL); } toolkit_clip(x, y, cst->w, cst->h); (*cst->dat.cst.render) (x, y, cst->w, cst->h); toolkit_unclip(); } // Render an input widget. static void toolkit_renderInput(Widget* inp, double bx, double by) { double x, y, ty; x = bx + inp->x; y = by + inp->y; // Main background. toolkit_drawRect(x, y, inp->w, inp->h, &cWhite, NULL); // Center vertically. if(inp->dat.inp.oneline) ty = y - (inp->h - gl_smallFont.h)/2.; gl_printText(&gl_smallFont, inp->w-10., inp->h, x+5. + gl_screen.w/2., ty + gl_screen.h/2., &cBlack, inp->dat.inp.input+inp->dat.inp.view); // Inner outline. toolkit_drawOutline(x, y, inp->w, inp->h, 0., toolkit_colLight, toolkit_col); // Outter outline. toolkit_drawOutline(x, y, inp->w, inp->h, 1., toolkit_colDark, NULL); } // Handle input for input widget. static int toolkit_inputInput(Uint8 type, Widget* inp, SDLKey key) { int n; SDLMod mods; if(inp->type != WIDGET_INPUT) return 0; mods = SDL_GetModState(); if(inp->dat.inp.oneline && isascii(key)) { // Backspace -> delete text. if((type == SDL_KEYDOWN) && (key == '\b') && (inp->dat.inp.pos > 0)) { inp->dat.inp.input[--inp->dat.inp.pos] = '\0'; if(inp->dat.inp.view > 0) { n = gl_printWidth(&gl_smallFont, inp->dat.inp.input + inp->dat.inp.view - 1); if(n+10 < inp->w) inp->dat.inp.view--; } } else if((type == SDL_KEYDOWN) && (inp->dat.inp.pos < inp->dat.inp.max-1)) { if((key == SDLK_RETURN) && !inp->dat.inp.oneline) inp->dat.inp.input[inp->dat.inp.pos++] = '\n'; // Uppder case characters. else if(isalpha(key) && (mods & (KMOD_LSHIFT | KMOD_RSHIFT))) inp->dat.inp.input[inp->dat.inp.pos++] = toupper(key); // Rest. else if(!iscntrl(key)) inp->dat.inp.input[inp->dat.inp.pos++] = key; // Didn't get a useful key. else return 0; n = gl_printWidth(&gl_smallFont, inp->dat.inp.input+inp->dat.inp.view); if(n + 10 > inp->w) inp->dat.inp.view++; return 1; } } return 0; } // 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); } // Toolkit input is handled here. // If we return 1, the input isn't passed. int toolkit_input(SDL_Event* event) { switch(event->type) { case SDL_MOUSEMOTION: case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: toolkit_mouseEvent(event); return 1; // Block input. case SDL_KEYDOWN: case SDL_KEYUP: return toolkit_keyEvent(event); } return 0; // Don't block unput. } // Input. static int mouse_down = 0; static void toolkit_mouseEvent(SDL_Event* event) { int i; double x, y; Window* w; Widget* wgt, *wgt_func; // Set mouse button status. if(event->type == SDL_MOUSEBUTTONDOWN) mouse_down = 1; else if(event->type == SDL_MOUSEBUTTONUP) mouse_down = 0; // 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]; // Always treat button ups to stop hanging states. if((event->type != SDL_MOUSEBUTTONUP) && ((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; wgt_func = NULL; for(i = 0; i < w->nwidgets; i++) { wgt = &w->widgets[i]; // Widget in range? if((x > wgt->x) && (x < (wgt->x + wgt->w)) && (y > wgt->y) && (y < (wgt->y + wgt->h))) { // Custom widgets take it from here. if((wgt->type == WIDGET_CUST) && wgt->dat.cst.mouse) (*wgt->dat.cst.mouse)(event, x-wgt->x, y-wgt->y); else switch(event->type) { case SDL_MOUSEMOTION: if(!mouse_down) wgt->status = WIDGET_STATUS_MOUSEOVER; break; case SDL_MOUSEBUTTONDOWN: wgt->status = WIDGET_STATUS_MOUSEDOWN; if(toolkit_isFocusable(wgt)) w->focus = i; if(wgt->type == WIDGET_LIST) toolkit_listFocus(wgt, x-wgt->x, y-wgt->y); break; case SDL_MOUSEBUTTONUP: if(wgt->status == WIDGET_STATUS_MOUSEDOWN) { if((wgt->type == WIDGET_BUTTON) && (wgt->dat.btn.disabled==0)) { if(wgt->dat.btn.fptr == NULL) DEBUG("Toolkit: Button '%s' of Window '%s'" "Does not have a function trigger", wgt->name, w->name); else wgt_func = wgt; // Run it at the end in case of close. } } wgt->status = WIDGET_STATUS_NORMAL; break; } } else if((wgt->type == WIDGET_CUST) && (event->type == SDL_MOUSEBUTTONUP) && wgt->dat.cst.mouse) (*wgt->dat.cst.mouse) (event, x-wgt->x, y-wgt->y); else if(!mouse_down) wgt->status = WIDGET_STATUS_NORMAL; } if(wgt_func) (*wgt_func->dat.btn.fptr)(wgt_func->name); } // Handle the key events. static SDLKey input_key; static unsigned int input_keyTime; static int input_keyCounter; static void toolkit_regKey(SDLKey key) { if((input_key == 0) && (input_keyTime == 0)) { input_key = key; input_keyTime = SDL_GetTicks(); input_keyCounter = 0; } } static void toolkit_unregKey(SDLKey key) { if(input_key == key) { input_key = 0; input_keyTime = 0; input_keyCounter = 0; } } static int toolkit_keyEvent(SDL_Event* event) { Window* wdw; Widget* wgt; SDLKey key; if(nwindows <= 0) return 0; wdw = &windows[nwindows-1]; wgt = (wdw->focus != -1) ? &wdw->widgets[wdw->focus] : NULL; key = event->key.keysym.sym; // Hack to simulate key repetition. if((key == SDLK_BACKSPACE) || isalnum(key)) { if(event->type == SDL_KEYDOWN) toolkit_regKey(key); else if(event->type == SDL_KEYUP) toolkit_unregKey(key); } // Handle input widgets. if(wgt && (wgt->type == WIDGET_INPUT)) // Grab all the events it wants. if(toolkit_inputInput(event->type, wgt, key)) return 1; switch(key) { case SDLK_TAB: if(event->type == SDL_KEYDOWN) toolkit_nextFocus(); return 1; case SDLK_RETURN: if(event->type == SDL_KEYDOWN) toolkit_triggerFocus(); return 1; case SDLK_UP: if(event->type == SDL_KEYDOWN) { toolkit_regKey(SDLK_UP); toolkit_listScroll(toolkit_getFocus(), +1); } else if(event->type == SDL_KEYUP) toolkit_unregKey(SDLK_UP); return 0; case SDLK_DOWN: if(event->type == SDL_KEYDOWN) { toolkit_regKey(SDLK_DOWN); toolkit_listScroll(toolkit_getFocus(), -1); } else if(event->type == SDL_KEYUP) toolkit_unregKey(SDLK_DOWN); return 0; default: return 0; } } void toolkit_update(void) { unsigned int t; Window* wdw; Widget* wgt; t = SDL_GetTicks(); if(input_key == 0) return; if(input_keyTime + INPUT_DELAY + input_keyCounter*INPUT_FREQ > t) return; input_keyCounter++; if(nwindows > 0) { wdw = &windows[nwindows-1]; wgt = (wdw->focus >= 0) ? &wdw->widgets[wdw->focus] : NULL; if(wgt && (wgt->type == WIDGET_INPUT) && (input_key == SDLK_BACKSPACE || isalnum(input_key))) toolkit_inputInput(SDL_KEYDOWN, wgt, input_key); } switch(input_key) { case SDLK_UP: toolkit_listScroll(toolkit_getFocus(), +1); break; case SDLK_DOWN: toolkit_listScroll(toolkit_getFocus(), -1); break; default: break; } } // Focus next widget. static void toolkit_nextFocus(void) { Window* wdw = &windows[nwindows-1]; // Get active window. if(wdw->nwidgets==0) wdw->focus = -1; else if(wdw->focus >= wdw->nwidgets) wdw->focus = -1; else if((++wdw->focus+1) && // Just increment. toolkit_isFocusable(&wdw->widgets[wdw->focus])) return; else toolkit_nextFocus(); } // Return 1 if the window is focusable. static int toolkit_isFocusable(Widget* wgt) { if(wgt == NULL) return 0; switch(wgt->type) { case WIDGET_BUTTON: if(wgt->dat.btn.disabled == 1) return 0; case WIDGET_LIST: case WIDGET_INPUT: return 1; default: return 0; } } static void toolkit_triggerFocus(void) { Window* wdw; Widget* wgt; wdw = &windows[nwindows-1]; if(wdw->focus == -1) return; wgt = &wdw->widgets[wdw->focus]; switch(wgt->type) { case WIDGET_BUTTON: if(wgt->dat.btn.fptr)(*wgt->dat.btn.fptr)(wgt->name); else DEBUG("Toolkit: Button '%s' of Window '%s'" "Doesn't have a function trigger", wgt->name, wdw->name); break; default: if(wdw->def_fptr)(*wdw->def_fptr)(wgt->name); break; } } // Try to scroll up/down by direction. static void toolkit_listScroll(Widget* wgt, int direction) { if(wgt == NULL) return; switch(wgt->type) { case WIDGET_LIST: wgt->dat.lst.selected -= direction; wgt->dat.lst.selected = MAX(0, wgt->dat.lst.selected); wgt->dat.lst.selected = MIN(wgt->dat.lst.selected, wgt->dat.lst.noptions-1); if(wgt->dat.lst.fptr) (*wgt->dat.lst.fptr)(wgt->name); break; default: break; } } // List mouse even focus. static void toolkit_listFocus(Widget* lst, double bx, double by) { (void)bx; int i; i = (lst->h - by) / (gl_defFont.h + 2.); if(i< lst->dat.lst.noptions) { // Should not be out of boundaries. lst->dat.lst.selected = i; toolkit_listScroll(lst, 0); // Check boundaries and trigger callback. } } // Get what is selected currently in a list. char* toolkit_getList(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); if((wgt->type != WIDGET_LIST) || (wgt->dat.lst.selected == -1)) return NULL; return wgt->dat.lst.options[wgt->dat.lst.selected]; } // Get the position of current item in the list. int toolkit_getListPos(const unsigned int wid, char* name) { Widget* wgt = window_getwgt(wid, name); if((wgt->type != WIDGET_LIST) || (wgt->dat.lst.selected == -1)) return -1; return wgt->dat.lst.selected; } // Return the focused widget. static Widget* toolkit_getFocus(void) { Window* wdw; wdw = &windows[nwindows-1]; if(wdw->focus == -1) return NULL; return &wdw->widgets[wdw->focus]; } void dialogue_alert(const char* fmt, ...) { char msg[512]; va_list ap; unsigned int wdw; int h; if(window_exists("Warning")) return; if(fmt == NULL) return; else { // Get the message. va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); } h = gl_printHeight(&gl_smallFont, 260, msg); // Create window. wdw = window_create("Warning", -1, -1, 300, 90+h); window_addText(wdw, 20, -30, 260, h, 0, "txtAlert", &gl_smallFont, &cBlack, msg); window_addButton(wdw, 135, 20, 50, 30, "btnOK", "OK", dialogue_alertClose); } static void dialogue_alertClose(char* str) { (void)str; if(window_exists("Warning")) window_destroy(window_get("Warning")); } static glFont* dialogue_getSize(char* msg, int* w, int* h) { glFont* font; font = &gl_smallFont; // Try to use smallfont. (*h) = gl_printHeight(font, (*w)-40, msg); if(strlen(msg) > 100) { // Make font bigger for large text area's. font = &gl_defFont; (*h) = gl_printHeight(font, (*w)-40, msg); if((*h) > 200) (*w) += MIN((*h)-200, 600); // Too big, so we make it wider. (*h) = gl_printHeight(font, (*w)-40, msg); } return font; } // Display an alert popup with only an OK button and a message. static unsigned int msg_wid = 0; void dialogue_msg(char* caption, const char* fmt, ...) { char msg[4096]; va_list ap; int w, h; glFont* font; if(msg_wid) return; if(fmt == NULL) return; else { // Get the message. va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); } w = 300; // Default width. font = dialogue_getSize(msg, &w, &h); // Create the window. msg_wid = window_create(caption, -1, -1, w, 110+h); window_addText(msg_wid, 20, -40, w-40, h, 0, "txtMsg", font, &cBlack, msg); window_addButton(msg_wid, (w-50)/2, 20, 50, 30, "btnOK", "OK", dialogue_msgClose); toolkit_loop(); } static void dialogue_msgClose(char* str) { (void)str; window_destroy(msg_wid); msg_wid = 0; loop_done = 1; } // Runs a dialogue with a Yes No button, return 1 if yes. static int yesno_result; static unsigned int yesno_wid = 0; int dialogue_YesNo(char* caption, const char* fmt, ...) { char msg[4096]; va_list ap; int w, h; glFont* font; if(yesno_wid) return -1; if(fmt == NULL) return -1; else { // Get the message. va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); } w = 300; font = dialogue_getSize(msg, &w, &h); // Create window. yesno_wid = window_create(caption, -1, -1, w, h+110); // Text. window_addText(yesno_wid, 20, -40, w-40, h, 0, "txtYesNo", font, &cBlack, msg); // Buttons. window_addButton(yesno_wid, w/2-50-10, 20, 50, 30, "btnYes", "Yes", dialogue_YesNoClose); window_addButton(yesno_wid, w/2+50+10, 20, 50, 30, "btnNo", "No", dialogue_YesNoClose); // Tricky secondary loop. toolkit_loop(); // Return the result. return yesno_result; } static void dialogue_YesNoClose(char* str) { // Store the result. if(strcmp(str, "btnYes")==0) yesno_result = 1; else if(strcmp(str, "btnNo")==0) yesno_result = 0; // Destroy the window. window_destroy(yesno_wid); yesno_wid = 0; loop_done = 1; } // Toolkit input boxes, return input. static unsigned int input_wid = 0; char* dialogue_input(char* title, int min, int max, const char* fmt, ...) { char msg[512], *input; va_list ap; int h; if(input_wid) return NULL; if(fmt == NULL) return NULL; else { // Get the message. va_start(ap, fmt); vsprintf(msg, fmt, ap); va_end(ap); } // Get text height. h = gl_printHeight(&gl_smallFont, 200, msg); // Create the window. input_wid = window_create(title, -1, -1, 240, h+140); window_setFptr(input_wid, dialogue_inputClose); // Text. window_addText(input_wid, 30, -30, 200, h, 0, "txtInput", &gl_smallFont, &cDConsole, msg); // Input. window_addInput(input_wid, 20, -50-h, 200, 20, "inpInput", max, 1); //Button. window_addButton(input_wid, -20, 20, 80, 30, "btnClose", "Done", dialogue_inputClose); // Tricky secondary loop. input = NULL; while(!input || ((int)strlen(input) < min)) { // Must be longer than min. if(input) { dialogue_alert("Input must be at least %d characters long!", min); free(input); input = NULL; } if(toolkit_loop()) // Error in loop -> quit. return NULL; // Save the input. input = strdup(window_getInput(input_wid, "inpInput")); } // Cleanup. window_destroy(input_wid); input_wid = 0; return input; } static void dialogue_inputClose(char* str) { (void)str; // Break the loop. loop_done = 1; } // 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); } } // Spawns a secondary loop that only works until the toolkit dies. // A lot like the main while loop in lephisto.c. static int toolkit_loop(void) { SDL_Event event, quit = { .type = SDL_QUIT }; loop_done = 0; while(!loop_done && toolkit) { while(SDL_PollEvent(&event)) { // Event loopz. if(event.type == SDL_QUIT) { // Pass quit event to main engine. loop_done = 1; SDL_PushEvent(&quit); return 1; } // Handles all the events and player keybinds. input_handle(&event); } main_loop(); } return 0; }