Lephisto/src/toolkit.c

746 lines
20 KiB
C

#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);
}
}