520 lines
14 KiB
C
520 lines
14 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
|
|
#include "gui.h"
|
|
|
|
/* macros */
|
|
#define MAX_BUFFER 64
|
|
#define MAX_MEMORY (8 * 1024)
|
|
#define MAX_PANELS 4
|
|
#define WIN_WIDTH 800
|
|
#define WIN_HEIGHT 600
|
|
#define DTIME 33
|
|
|
|
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
|
#define MAX(a,b) ((a) < (b) ? (b) : (a))
|
|
#define CLAMP(i,v,x) (MAX(MIN(v,x), i))
|
|
#define LEN(a) (sizeof(a)/sizeof(a)[0])
|
|
#define UNUSED(a) ((void)(a))
|
|
|
|
/* Types */
|
|
typedef struct XFont {
|
|
HFONT handle;
|
|
int height;
|
|
} XFont;
|
|
|
|
typedef struct XSurface {
|
|
int save;
|
|
HDC hdc;
|
|
HBITMAP bitmap;
|
|
HRGN region;
|
|
unsigned int width;
|
|
unsigned int height;
|
|
} XSurface;
|
|
|
|
typedef struct XWindow {
|
|
HINSTANCE hInstance;
|
|
WNDCLASS wc;
|
|
HWND hWnd;
|
|
HDC hdc;
|
|
RECT rect;
|
|
XSurface *backbuffer;
|
|
XFont *font;
|
|
} XWindow;
|
|
|
|
struct demo {
|
|
gui_char in_buf[MAX_BUFFER];
|
|
gui_size in_len;
|
|
gui_bool in_act;
|
|
gui_bool check;
|
|
gui_int option;
|
|
gui_float slider;
|
|
gui_size prog;
|
|
gui_int spinner;
|
|
gui_bool spin_act;
|
|
gui_size item_cur;
|
|
gui_size cur;
|
|
gui_bool tab_min;
|
|
gui_float group_off;
|
|
gui_float shelf_off;
|
|
gui_bool toggle;
|
|
};
|
|
|
|
static int running = 1;
|
|
|
|
static void
|
|
die(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
static long long
|
|
timestamp(LARGE_INTEGER freq)
|
|
{
|
|
LARGE_INTEGER now;
|
|
QueryPerformanceCounter(&now);
|
|
return (1000LL * now.QuadPart) / freq.QuadPart;
|
|
}
|
|
|
|
|
|
static XFont*
|
|
font_new(const char *name, int height)
|
|
{
|
|
XFont *font = malloc(sizeof(XFont));
|
|
font->height = height;
|
|
font->handle = CreateFont(height, 0, 0, 0, 0, FALSE, FALSE, FALSE,
|
|
ANSI_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS,
|
|
ANTIALIASED_QUALITY, DEFAULT_PITCH, name);
|
|
return font;
|
|
}
|
|
|
|
gui_size
|
|
font_get_text_width(void *handle, const gui_char *text, gui_size len)
|
|
{
|
|
SIZE size;
|
|
HFONT old;
|
|
XWindow *win = handle;
|
|
XFont *font = win->font;
|
|
XSurface *surf = win->backbuffer;
|
|
if (!font ||!text || !len) return 0;
|
|
|
|
old = SelectObject(surf->hdc, font->handle);
|
|
GetTextExtentPoint32(surf->hdc, (LPCTSTR)text, len, &size);
|
|
SelectObject(surf->hdc, old);
|
|
return size.cx;
|
|
}
|
|
|
|
static void
|
|
font_del(XFont *font)
|
|
{
|
|
if (font->handle)
|
|
DeleteObject(font->handle);
|
|
free(font);
|
|
}
|
|
|
|
static XSurface*
|
|
surface_new(HDC hdc, unsigned int width, unsigned int height)
|
|
{
|
|
XSurface *surf = malloc(sizeof(XSurface));
|
|
surf->hdc = CreateCompatibleDC(hdc);
|
|
surf->bitmap = CreateCompatibleBitmap(hdc, width, height);
|
|
surf->width = width;
|
|
surf->height = height;
|
|
surf->region = NULL;
|
|
return surf;
|
|
}
|
|
|
|
static void
|
|
surface_resize(XSurface *surf, HDC hdc, unsigned int width, unsigned int height)
|
|
{
|
|
DeleteObject(surf->bitmap);
|
|
surf->bitmap = CreateCompatibleBitmap(hdc, width, height);
|
|
surf->width = width;
|
|
surf->height = height;
|
|
}
|
|
|
|
static void
|
|
surface_del(XSurface *surf)
|
|
{
|
|
if (surf->region) DeleteObject(surf->region);
|
|
DeleteDC(surf->hdc);
|
|
DeleteObject(surf->bitmap);
|
|
free(surf);
|
|
}
|
|
|
|
static void
|
|
surface_scissor(XSurface *surf, short x, short y, unsigned short w, unsigned short h)
|
|
{
|
|
if (surf->region) DeleteObject(surf->region);
|
|
surf->region = CreateRectRgn(x, y, x + w, y + h);
|
|
SelectClipRgn(surf->hdc, surf->region);
|
|
}
|
|
|
|
static void
|
|
surface_draw_line(XSurface *surf, short x0, short y0, short x1, short y1,
|
|
unsigned char r, unsigned char g, unsigned char b)
|
|
{
|
|
HPEN hPen;
|
|
HPEN old;
|
|
/* TODO(vurtun): This brush allocation is literally bad shit INSANE!
|
|
* There has to be a better way to do this! */
|
|
hPen = CreatePen(PS_SOLID, 1, RGB(r,g,b));
|
|
old = SelectObject(surf->hdc, hPen);
|
|
MoveToEx(surf->hdc, x0, y0, NULL);
|
|
LineTo(surf->hdc, x1, y1);
|
|
SelectObject(surf->hdc, old);
|
|
DeleteObject(hPen);
|
|
}
|
|
|
|
static void
|
|
surface_draw_rect(XSurface *surf, short x, short y, unsigned short w, unsigned short h,
|
|
unsigned char r, unsigned char g, unsigned char b)
|
|
{
|
|
HBRUSH hBrush;
|
|
HBRUSH old;
|
|
RECT rect;
|
|
rect.left = (LONG)x;
|
|
rect.top = (LONG)y;
|
|
rect.right = (LONG)(x + w);
|
|
rect.bottom = (LONG)(y + h);
|
|
hBrush = CreateSolidBrush(RGB(r,g,b));
|
|
old = SelectObject(surf->hdc, hBrush);
|
|
FillRect(surf->hdc, &rect, hBrush);
|
|
SelectObject(surf->hdc, old);
|
|
DeleteObject(hBrush);
|
|
}
|
|
|
|
static void
|
|
surface_draw_circle(XSurface *surf, short x, short y, unsigned short w, unsigned short h,
|
|
unsigned char r, unsigned char g, unsigned char b)
|
|
{
|
|
HBRUSH hBrush;
|
|
HBRUSH old;
|
|
hBrush = CreateSolidBrush(RGB(r,g,b));
|
|
old = SelectObject(surf->hdc, hBrush);
|
|
Ellipse(surf->hdc, x, y, (x + w), (y + h));
|
|
SelectObject(surf->hdc, old);
|
|
DeleteObject(hBrush);
|
|
}
|
|
|
|
static void
|
|
surface_draw_triangle(XSurface *surf, short x0, short y0, short x1, short y1,
|
|
short x2, short y2, unsigned char r, unsigned char g, unsigned char b)
|
|
{
|
|
HBRUSH old;
|
|
HBRUSH hBrush;
|
|
POINT pnt[3];
|
|
pnt[0].x = (LONG)x0;
|
|
pnt[0].y = (LONG)y0;
|
|
pnt[1].x = (LONG)x1;
|
|
pnt[1].y = (LONG)y1;
|
|
pnt[2].x = (LONG)x2;
|
|
pnt[2].y = (LONG)y2;
|
|
|
|
hBrush = CreateSolidBrush(RGB(r,g,b));
|
|
old = SelectObject(surf->hdc, hBrush);
|
|
Polygon(surf->hdc, pnt, 3);
|
|
SelectObject(surf->hdc, old);
|
|
DeleteObject(hBrush);
|
|
}
|
|
|
|
static void
|
|
surface_draw_text(XSurface *surf, XFont *font, short x, short y, unsigned short w, unsigned short h,
|
|
const char *text, unsigned int len,
|
|
unsigned char bg_r, unsigned char bg_g, unsigned char bg_b,
|
|
unsigned char fg_r, unsigned char fg_g, unsigned char fg_b)
|
|
{
|
|
RECT format;
|
|
UINT bg = RGB(bg_r, bg_g, bg_b);
|
|
UINT fg = RGB(fg_r, fg_g, fg_b);
|
|
HFONT old = SelectObject(surf->hdc, font->handle);
|
|
format.left = x;
|
|
format.top = y;
|
|
format.right = x + w;
|
|
format.bottom = y + h;
|
|
|
|
SetBkColor(surf->hdc, bg);
|
|
SetTextColor(surf->hdc, fg);
|
|
SetBkMode(surf->hdc, OPAQUE);
|
|
DrawText(surf->hdc, text, len, &format, DT_LEFT);
|
|
SelectObject(surf->hdc, old);
|
|
}
|
|
|
|
static void
|
|
surface_clear(XSurface *surf, unsigned char r, unsigned char g, unsigned char b)
|
|
{surface_draw_rect(surf, 0, 0, (unsigned short)surf->width, (unsigned short)surf->height, r, g, b);}
|
|
|
|
static void
|
|
surface_begin(XSurface *surf)
|
|
{
|
|
surf->save = SaveDC(surf->hdc);
|
|
SelectObject(surf->hdc, surf->bitmap);
|
|
}
|
|
|
|
static void
|
|
surface_end(XSurface *surf, HDC hdc)
|
|
{
|
|
BitBlt(hdc, 0, 0, surf->width, surf->height, surf->hdc, 0, 0, SRCCOPY);
|
|
RestoreDC(hdc, surf->save);
|
|
}
|
|
|
|
static void
|
|
draw(XSurface *surf, struct gui_command_list *list)
|
|
{
|
|
struct gui_command *cmd;
|
|
if (!list->count) return;
|
|
cmd = list->begin;
|
|
while (cmd != list->end) {
|
|
switch (cmd->type) {
|
|
case GUI_COMMAND_NOP: break;
|
|
case GUI_COMMAND_SCISSOR: {
|
|
struct gui_command_scissor *s = (void*)cmd;
|
|
surface_scissor(surf, s->x, s->y, s->w, s->h);
|
|
} break;
|
|
case GUI_COMMAND_LINE: {
|
|
struct gui_command_line *l = (void*)cmd;
|
|
surface_draw_line(surf, l->begin[0], l->begin[1], l->end[0],
|
|
l->end[1], l->color.r, l->color.g, l->color.b);
|
|
} break;
|
|
case GUI_COMMAND_RECT: {
|
|
struct gui_command_rect *r = (void*)cmd;
|
|
surface_draw_rect(surf, r->x, r->y, r->w, r->h,
|
|
r->color.r, r->color.g, r->color.b);
|
|
} break;
|
|
case GUI_COMMAND_CIRCLE: {
|
|
struct gui_command_circle *c = (void*)cmd;
|
|
surface_draw_circle(surf, c->x, c->y, c->w, c->h,
|
|
c->color.r, c->color.g, c->color.b);
|
|
} break;
|
|
case GUI_COMMAND_TRIANGLE: {
|
|
struct gui_command_triangle *t = (void*)cmd;
|
|
surface_draw_triangle(surf, t->a[0], t->a[1], t->b[0], t->b[1],
|
|
t->c[0], t->c[1], t->color.r, t->color.g, t->color.b);
|
|
} break;
|
|
case GUI_COMMAND_TEXT: {
|
|
struct gui_command_text *t = (void*)cmd;
|
|
surface_draw_text(surf, t->font, t->x, t->y, t->w, t->h, (const char*)t->string,
|
|
t->length, t->bg.r, t->bg.g, t->bg.b, t->fg.r, t->fg.g, t->fg.b);
|
|
} break;
|
|
default: break;
|
|
}
|
|
cmd = cmd->next;
|
|
}
|
|
}
|
|
|
|
static void
|
|
demo_panel(struct gui_panel_layout *panel, struct demo *demo)
|
|
{
|
|
gui_int i = 0;
|
|
enum {HISTO, PLOT};
|
|
const char *shelfs[] = {"Histogram", "Lines"};
|
|
const gui_float values[] = {8.0f, 15.0f, 20.0f, 12.0f, 30.0f};
|
|
const char *items[] = {"Fist", "Pistol", "Shotgun", "Railgun", "BFG"};
|
|
const char *options[] = {"easy", "normal", "hard", "hell", "doom", "godlike"};
|
|
struct gui_panel_layout tab;
|
|
|
|
/* Tabs */
|
|
demo->tab_min = gui_panel_tab_begin(panel, &tab, "Difficulty", demo->tab_min);
|
|
gui_panel_row(&tab, 30, 3);
|
|
for (i = 0; i < (gui_int)LEN(options); i++) {
|
|
if (gui_panel_option(&tab, options[i], demo->option == i))
|
|
demo->option = i;
|
|
}
|
|
gui_panel_tab_end(panel, &tab);
|
|
|
|
/* Shelf */
|
|
gui_panel_row(panel, 200, 2);
|
|
demo->cur = gui_panel_shelf_begin(panel,&tab,shelfs,LEN(shelfs),demo->cur,demo->shelf_off);
|
|
gui_panel_row(&tab, 100, 1);
|
|
if (demo->cur == HISTO) {
|
|
gui_panel_histo(&tab, values, LEN(values));
|
|
} else {
|
|
gui_panel_plot(&tab, values, LEN(values));
|
|
}
|
|
demo->shelf_off = gui_panel_shelf_end(panel, &tab);
|
|
|
|
/* Group */
|
|
gui_panel_group_begin(panel, &tab, "Options", demo->group_off);
|
|
gui_panel_row(&tab, 30, 1);
|
|
if (gui_panel_button_text(&tab, "button", GUI_BUTTON_DEFAULT))
|
|
fprintf(stdout, "button pressed!\n");
|
|
demo->toggle = gui_panel_button_toggle(&tab, "toggle", demo->toggle);
|
|
demo->check = gui_panel_check(&tab, "advanced", demo->check);
|
|
demo->slider = gui_panel_slider(&tab, 0, demo->slider, 10, 1.0f);
|
|
demo->prog = gui_panel_progress(&tab, demo->prog, 100, gui_true);
|
|
demo->item_cur = gui_panel_selector(&tab, items, LEN(items), demo->item_cur);
|
|
demo->spinner = gui_panel_spinner(&tab, 0, demo->spinner, 250, 10, &demo->spin_act);
|
|
demo->in_len = gui_panel_input(&tab,demo->in_buf,demo->in_len,
|
|
MAX_BUFFER,&demo->in_act,GUI_INPUT_DEFAULT);
|
|
demo->group_off = gui_panel_group_end(panel, &tab);
|
|
}
|
|
|
|
static void
|
|
key(struct gui_input *in, MSG *msg, gui_bool down)
|
|
{
|
|
if (msg->lParam == VK_CONTROL)
|
|
gui_input_key(in, GUI_KEY_CTRL, down);
|
|
else if (msg->lParam == VK_SHIFT)
|
|
gui_input_key(in, GUI_KEY_SHIFT, down);
|
|
else if (msg->lParam == VK_DELETE)
|
|
gui_input_key(in, GUI_KEY_DEL, down);
|
|
else if (msg->lParam == VK_RETURN)
|
|
gui_input_key(in, GUI_KEY_ENTER, down);
|
|
else if (msg->lParam == VK_SPACE)
|
|
gui_input_key(in, GUI_KEY_SPACE, down);
|
|
else if (msg->lParam == VK_BACK)
|
|
gui_input_key(in, GUI_KEY_BACKSPACE, down);
|
|
}
|
|
|
|
static void
|
|
text(struct gui_input *in, MSG *msg)
|
|
{
|
|
gui_glyph glyph;
|
|
if (msg->wParam < 32 && msg->wParam >= 128) return;
|
|
glyph[0] = (gui_char)msg->wParam;
|
|
gui_input_char(in, glyph);
|
|
}
|
|
|
|
static void
|
|
motion(struct gui_input *in, MSG *msg)
|
|
{
|
|
const gui_int x = GET_X_LPARAM(msg->lParam);
|
|
const gui_int y = GET_Y_LPARAM(msg->lParam);
|
|
gui_input_motion(in, x, y);
|
|
}
|
|
|
|
static void
|
|
btn(struct gui_input *in, MSG *msg, gui_bool down)
|
|
{
|
|
const gui_int x = GET_X_LPARAM(msg->lParam);
|
|
const gui_int y = GET_Y_LPARAM(msg->lParam);
|
|
gui_input_button(in, x, y, down);
|
|
}
|
|
|
|
LRESULT CALLBACK
|
|
wnd_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (msg) {
|
|
case WM_DESTROY:
|
|
PostQuitMessage(WM_QUIT);
|
|
running = 0;
|
|
break;
|
|
default:
|
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
INT WINAPI
|
|
WinMain(HINSTANCE hInstance, HINSTANCE prev, LPSTR lpCmdLine, int show)
|
|
{
|
|
LARGE_INTEGER freq;
|
|
long long start;
|
|
long long dt;
|
|
struct demo demo;
|
|
|
|
/* GUI */
|
|
struct gui_input in;
|
|
struct gui_font font;
|
|
struct gui_memory memory;
|
|
struct gui_memory_status status;
|
|
struct gui_config config;
|
|
struct gui_canvas canvas;
|
|
struct gui_command_buffer buffer;
|
|
struct gui_command_list list;
|
|
struct gui_panel_layout layout;
|
|
struct gui_panel panel;
|
|
|
|
/* Window */
|
|
XWindow xw;
|
|
memset(&xw, 0, sizeof(xw));
|
|
xw.wc.style = CS_HREDRAW|CS_VREDRAW;
|
|
xw.wc.lpfnWndProc = wnd_proc;
|
|
xw.wc.hInstance = hInstance;
|
|
xw.wc.lpszClassName = "GUI";
|
|
RegisterClass(&xw.wc);
|
|
xw.hWnd = CreateWindowEx(
|
|
0, xw.wc.lpszClassName, "Demo",
|
|
WS_OVERLAPPEDWINDOW|WS_VISIBLE,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
0, 0, hInstance, 0);
|
|
|
|
QueryPerformanceFrequency(&freq);
|
|
xw.hdc = GetDC(xw.hWnd);
|
|
GetClientRect(xw.hWnd, &xw.rect);
|
|
xw.backbuffer = surface_new(xw.hdc, xw.rect.right, xw.rect.bottom);
|
|
xw.font = font_new("Times New Roman", 12);
|
|
|
|
/* GUI */
|
|
memset(&in, 0, sizeof in);
|
|
memory.memory = calloc(MAX_MEMORY, 1);
|
|
memory.size = MAX_MEMORY;
|
|
gui_buffer_init_fixed(&buffer, &memory, GUI_CLIP);
|
|
|
|
font.userdata = &xw;
|
|
font.height = (gui_float)xw.font->height;
|
|
font.width = font_get_text_width;
|
|
gui_default_config(&config);
|
|
gui_panel_init(&panel, 50, 50, 420, 300,
|
|
GUI_PANEL_BORDER|GUI_PANEL_MOVEABLE|
|
|
GUI_PANEL_CLOSEABLE|GUI_PANEL_SCALEABLE|
|
|
GUI_PANEL_MINIMIZABLE, &config, &font);
|
|
|
|
/* Demo */
|
|
memset(&demo, 0, sizeof(demo));
|
|
demo.tab_min = gui_true;
|
|
demo.spinner = 100;
|
|
demo.slider = 2.0f;
|
|
demo.prog = 60;
|
|
|
|
while (running) {
|
|
/* Input */
|
|
MSG msg;
|
|
start = timestamp(freq);
|
|
gui_input_begin(&in);
|
|
while (PeekMessage(&msg, xw.hWnd, 0, 0, PM_REMOVE)) {
|
|
if (msg.message == WM_KEYDOWN) key(&in, &msg, gui_true);
|
|
else if (msg.message == WM_KEYUP) key(&in, &msg, gui_false);
|
|
else if (msg.message == WM_LBUTTONDOWN) btn(&in, &msg, gui_true);
|
|
else if (msg.message == WM_LBUTTONUP) btn(&in, &msg, gui_false);
|
|
else if (msg.message == WM_MOUSEMOVE) motion(&in, &msg);
|
|
else if (msg.message == WM_CHAR) text(&in, &msg);
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
gui_input_end(&in);
|
|
|
|
/* GUI */
|
|
gui_buffer_begin(&canvas, &buffer, xw.backbuffer->width, xw.backbuffer->height);
|
|
running = gui_panel_begin(&layout, &panel, "Demo", &canvas, &in);
|
|
demo_panel(&layout, &demo);
|
|
gui_panel_end(&layout, &panel);
|
|
gui_buffer_end(&list, &buffer, &canvas, &status);
|
|
|
|
/* Draw */
|
|
surface_begin(xw.backbuffer);
|
|
surface_clear(xw.backbuffer, 255, 255, 255);
|
|
draw(xw.backbuffer, &list);
|
|
surface_end(xw.backbuffer, xw.hdc);
|
|
|
|
/* Timing */
|
|
dt = timestamp(freq) - start;
|
|
if (dt < DTIME) Sleep(DTIME - dt);
|
|
}
|
|
|
|
font_del(xw.font);
|
|
surface_del(xw.backbuffer);
|
|
ReleaseDC(xw.hWnd, xw.hdc);
|
|
return 0;
|
|
}
|
|
|