1670 lines
44 KiB
C
1670 lines
44 KiB
C
#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;
|
|
}
|
|
|