Nuklear/x11.c
2015-03-05 19:50:56 +01:00

477 lines
14 KiB
C

/*
Copyright (c) 2015
vurtun <polygone@gmx.net>
MIT licence
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
#include <memory.h>
#include <assert.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>
#include "gui.h"
/* macros */
#define WIN_WIDTH 800
#define WIN_HEIGHT 600
#define DTIME 33
#define MAX_VERTEX_BUFFER (16 * 1024)
#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))
#define glerror() glerror_(__FILE__, __LINE__)
/* types */
struct XWindow {
Display *dpy;
Window root;
XVisualInfo *vi;
Colormap cmap;
XSetWindowAttributes swa;
XWindowAttributes gwa;
Window win;
GLXContext glc;
int running;
};
struct GUI {
struct XWindow *win;
struct gui_draw_list out;
struct gui_input input;
struct gui_font *font;
};
/* functions */
static void die(const char*,...);
static void* xcalloc(size_t nmemb, size_t size);
static long timestamp(void);
static void sleep_for(long ms);
static char* ldfile(const char*, int, size_t*);
static void kpress(struct GUI*, XEvent*);
static void bpress(struct GUI*, XEvent*);
static void brelease(struct GUI*, XEvent*);
static void bmotion(struct GUI*, XEvent*);
static void resize(struct GUI*, XEvent*);
static struct gui_font *ldfont(const char*, unsigned char);
static void delfont(struct gui_font *font);
static void draw(struct GUI*, int, int , const struct gui_draw_list*);
/* gobals */
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
exit(EXIT_FAILURE);
}
static void*
xcalloc(size_t nmemb, size_t size)
{
void *p = calloc(nmemb, size);
if (!p)
die("out of memory\n");
return p;
}
static long
timestamp(void)
{
struct timeval tv;
if (gettimeofday(&tv, NULL) < 0) return 0;
return (long)((long)tv.tv_sec * 1000 + (long)tv.tv_usec/1000);
}
static void
sleep_for(long t)
{
const time_t sec = (int)(t/1000);
const long ms = t - (sec * 1000);
struct timespec req;
req.tv_sec = sec;
req.tv_nsec = ms * 1000000L;
while(-1 == nanosleep(&req, &req));
}
static void
kpress(struct GUI *con, XEvent* e)
{
int ret;
struct XWindow *xw = con->win;
KeySym *keysym = XGetKeyboardMapping(xw->dpy, e->xkey.keycode, 1, &ret);
if (*keysym == XK_Escape) xw->running = 0;
else if (*keysym == XK_Shift_L || *keysym == XK_Shift_R)
gui_input_key(&con->input, GUI_KEY_SHIFT, gui_true);
else if (*keysym == XK_Control_L || *keysym == XK_Control_L)
gui_input_key(&con->input, GUI_KEY_CTRL, gui_true);
else if (*keysym == XK_Delete)
gui_input_key(&con->input, GUI_KEY_DEL, gui_true);
else if (*keysym == XK_Return)
gui_input_key(&con->input, GUI_KEY_ENTER, gui_true);
else if (*keysym == XK_BackSpace)
gui_input_key(&con->input, GUI_KEY_BACKSPACE, gui_true);
}
static void
krelease(struct GUI *con, XEvent* e)
{
int ret;
struct XWindow *xw = con->win;
KeySym *keysym = XGetKeyboardMapping(xw->dpy, e->xkey.keycode, 1, &ret);
if (*keysym == XK_Shift_L || *keysym == XK_Shift_R)
gui_input_key(&con->input, GUI_KEY_SHIFT, gui_false);
else if (*keysym == XK_Control_L || *keysym == XK_Control_L)
gui_input_key(&con->input, GUI_KEY_CTRL, gui_false);
else if (*keysym == XK_Delete)
gui_input_key(&con->input, GUI_KEY_DEL, gui_false);
else if (*keysym == XK_Return)
gui_input_key(&con->input, GUI_KEY_ENTER, gui_false);
else if (*keysym == XK_BackSpace)
gui_input_key(&con->input, GUI_KEY_BACKSPACE, gui_false);
}
static void
bpress(struct GUI *con, XEvent *evt)
{
const float x = evt->xbutton.x;
const float y = evt->xbutton.y;
if (evt->xbutton.button == Button1)
gui_input_button(&con->input, x, y, gui_true);
}
static void
brelease(struct GUI *con, XEvent *evt)
{
const float x = evt->xbutton.x;
const float y = evt->xbutton.y;
struct XWindow *xw = con->win;
if (evt->xbutton.button == Button1)
gui_input_button(&con->input, x, y, gui_false);
}
static void
bmotion(struct GUI *con, XEvent *evt)
{
const gui_int x = evt->xbutton.x;
const gui_int y = evt->xbutton.y;
struct XWindow *xw = con->win;
gui_input_motion(&con->input, x, y);
}
static void
resize(struct GUI *con, XEvent* evt)
{
struct XWindow *xw = con->win;
XGetWindowAttributes(xw->dpy, xw->win, &xw->gwa);
glViewport(0, 0, xw->gwa.width, xw->gwa.height);
}
static char*
ldfile(const char* path, int flags, size_t* siz)
{
char *buf;
int fd = open(path, flags);
struct stat status;
if (fd == -1)
die("Failed to open file: %s (%s)\n",
path, strerror(errno));
if (fstat(fd, &status) < 0)
die("Failed to call fstat: %s",strerror(errno));
*siz = status.st_size;
buf = xcalloc(*siz, 1);
if (read(fd, buf, *siz) < 0)
die("Failed to call read: %s",strerror(errno));
close(fd);
return buf;
}
static void
glerror_(const char *file, int line)
{
const GLenum code = glGetError();
if (code == GL_INVALID_ENUM)
fprintf(stdout, "[GL] Error: (%s:%d) invalid value!\n", file, line);
else if (code == GL_INVALID_OPERATION)
fprintf(stdout, "[GL] Error: (%s:%d) invalid operation!\n", file, line);
else if (code == GL_INVALID_FRAMEBUFFER_OPERATION)
fprintf(stdout, "[GL] Error: (%s:%d) invalid frame op!\n", file, line);
else if (code == GL_OUT_OF_MEMORY)
fprintf(stdout, "[GL] Error: (%s:%d) out of memory!\n", file, line);
else if (code == GL_STACK_UNDERFLOW)
fprintf(stdout, "[GL] Error: (%s:%d) stack underflow!\n", file, line);
else if (code == GL_STACK_OVERFLOW)
fprintf(stdout, "[GL] Error: (%s:%d) stack overflow!\n", file, line);
}
static struct gui_font*
ldfont(const char *name, unsigned char height)
{
union conversion {
gui_texture handle;
uintptr_t ptr;
} convert;
GLuint texture;
size_t size;
struct gui_font *font;
short i = 0;
uint32_t bpp;
short max_height = 0;
uint32_t ioff;
/* header */
unsigned char *buffer = (unsigned char*)ldfile(name, O_RDONLY, &size);
uint16_t num = *(uint16_t*)buffer;
uint16_t indexes = *(uint16_t*)&buffer[0x02] + 1;
uint16_t tex_width = *(uint16_t*)&buffer[0x04];
uint16_t tex_height = *(uint16_t*)&buffer[0x06];
/* glyphes */
unsigned char *header;
unsigned char *data;
unsigned char *iter = &buffer[0x08];
size_t mem = sizeof(struct gui_font_glyph) * indexes;
struct gui_font_glyph *glyphes = xcalloc(mem, 1);
for(i = 0; i < num; ++i) {
short id = *(uint16_t*)&iter[0];
short x = *(uint16_t*)&iter[2];
short y = *(uint16_t*)&iter[4];
short w = *(uint16_t*)&iter[6];
short h = *(uint16_t*)&iter[8];
glyphes[id].code = id;
glyphes[id].width = w;
glyphes[id].height = h;
glyphes[id].xoff = *(float*)&iter[10];
glyphes[id].yoff = *(float*)&iter[14];
glyphes[id].xadvance = *(float*)&iter[18];
glyphes[id].uv[0].u = (gui_float)x/(gui_float)tex_width;
glyphes[id].uv[0].v = (gui_float)(y+h)/(gui_float)tex_height;
glyphes[id].uv[1].u = (gui_float)(x+w)/(gui_float)tex_width;
glyphes[id].uv[1].v = (gui_float)y/(gui_float)tex_height;
if (glyphes[id].height > max_height) max_height = glyphes[id].height;
iter += 22;
}
/* texture */
header = iter;
assert(header[0] == 'B');
assert(header[1] == 'M');
ioff = *(uint32_t*)(&header[0x0A]);
data = iter + ioff;
glGenTextures(1, &texture);
convert.ptr = texture;
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex_width, tex_height, 0,
GL_BGRA, GL_UNSIGNED_BYTE, data);
glerror();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
/* font */
font = xcalloc(sizeof(struct gui_font), 1);
font->height = height;
font->scale = (float)height/(float)max_height;
font->texture = convert.handle;
font->tex_size.x = tex_width;
font->tex_size.y = tex_height;
font->fallback = &glyphes['?'];
font->glyphes = glyphes;
font->glyph_count = indexes;
free(buffer);
return font;
}
static void
delfont(struct gui_font *font)
{
if (!font) return;
if (font->glyphes)
free(font->glyphes);
free(font);
}
static void
draw(struct GUI *con, int width, int height, const struct gui_draw_list *list)
{
const struct gui_draw_command *cmd;
static const size_t v = sizeof(struct gui_vertex);
static const size_t p = offsetof(struct gui_vertex, pos);
static const size_t t = offsetof(struct gui_vertex, uv);
static const size_t c = offsetof(struct gui_vertex, color);
if (!list) return;
glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnable(GL_TEXTURE_2D);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(0.0f, width, height, 0.0f, 0.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
cmd = list->begin;
while (cmd) {
const int x = (int)cmd->clip_rect.x;
const int y = height - (int)(cmd->clip_rect.y + cmd->clip_rect.h);
const int w = (int)cmd->clip_rect.w;
const int h = (int)cmd->clip_rect.h;
gui_byte *buffer = (gui_byte*)cmd->vertexes;
glVertexPointer(2, GL_FLOAT, v, (void*)(buffer + p));
glTexCoordPointer(2, GL_FLOAT, v, (void*)(buffer + t));
glColorPointer(4, GL_UNSIGNED_BYTE, v, (void*)(buffer + c));
glBindTexture(GL_TEXTURE_2D, (unsigned long)cmd->texture);
glScissor(x, y, w, h);
glDrawArrays(GL_TRIANGLES, 0, cmd->vertex_count);
cmd = gui_next(list, cmd);
}
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
int
main(int argc, char *argv[])
{
struct XWindow xw;
struct GUI gui;
long dt, started;
gui_byte *buffer;
gui_size buffer_size = MAX_VERTEX_BUFFER;
const struct gui_color colorA = {100, 100, 100, 255};
const struct gui_color colorB = {45, 45, 45, 255};
const struct gui_color colorC = {0, 0, 0, 255};
static GLint att[] = {GLX_RGBA, GLX_DEPTH_SIZE,24, GLX_DOUBLEBUFFER, None};
GLenum err;
gui_float slider = 5.0f;
gui_float prog = 60.0f;
gui_float offset = 300;
/* x11 */
UNUSED(argc); UNUSED(argv);
memset(&xw, 0, sizeof xw);
memset(&gui, 0, sizeof gui);
xw.dpy = XOpenDisplay(NULL);
if (!xw.dpy)
die("XOpenDisplay failed\n");
xw.root = DefaultRootWindow(xw.dpy);
xw.vi = glXChooseVisual(xw.dpy, 0, att);
if (!xw.vi)
die("Failed to find appropriate visual\n");
xw.cmap = XCreateColormap(xw.dpy,xw.root,xw.vi->visual,AllocNone);
xw.swa.colormap = xw.cmap;
xw.swa.event_mask =
ExposureMask | KeyPressMask | ButtonPress |
ButtonReleaseMask | ButtonMotionMask | PointerMotionMask |
Button1MotionMask | Button2MotionMask | Button3MotionMask;
xw.win = XCreateWindow(
xw.dpy, xw.root, 0, 0,WIN_WIDTH,WIN_HEIGHT, 0,
xw.vi->depth, InputOutput, xw.vi->visual,
CWColormap | CWEventMask, &xw.swa);
XGetWindowAttributes(xw.dpy, xw.win, &xw.gwa);
XStoreName(xw.dpy, xw.win, "Demo");
XMapWindow(xw.dpy, xw.win);
XFlush(xw.dpy);
XSync(xw.dpy, False);
/* OpenGL */
xw.glc = glXCreateContext(xw.dpy, xw.vi, NULL, GL_TRUE);
glXMakeCurrent(xw.dpy, xw.win, xw.glc);
buffer = xcalloc(MAX_VERTEX_BUFFER, 1);
xw.running = 1;
gui.win = &xw;
gui.font = ldfont("mono.font", 12);
while (xw.running) {
XEvent ev;
started = timestamp();
gui_input_begin(&gui.input);
while (XCheckWindowEvent(xw.dpy, xw.win, xw.swa.event_mask, &ev)) {
if (ev.type == Expose||ev.type == ConfigureNotify) resize(&gui, &ev);
else if (ev.type == MotionNotify) bmotion(&gui, &ev);
else if (ev.type == ButtonPress) bpress(&gui, &ev);
else if (ev.type == ButtonRelease) brelease(&gui, &ev);
else if (ev.type == KeyPress) kpress(&gui, &ev);
else if (ev.type == KeyRelease) krelease(&gui, &ev);
}
gui_input_end(&gui.input);
/* ------------------------- GUI --------------------------*/
gui_begin(&gui.out, buffer, MAX_VERTEX_BUFFER);
if (gui_button(&gui.out, &gui.input, gui.font, colorA, colorC, 50,50,150,30,5,"button",6))
fprintf(stdout, "Button pressed!\n");
slider = gui_slider(&gui.out, &gui.input, colorA, colorB,
50, 100, 150, 30, 2, 0.0f, slider, 10.0f, 1.0f);
prog = gui_progress(&gui.out, &gui.input, colorA, colorB,
50, 150, 150, 30, 2, prog, 100.0f, gui_true);
offset = gui_scroll(&gui.out, &gui.input, colorA, colorB,
250, 50, 16, 300, offset, 600);
gui_end(&gui.out);
/* ---------------------------------------------------------*/
/* Draw */
glClearColor(45.0f/255.0f,45.0f/255.0f,45.0f/255.0f,1);
glClear(GL_COLOR_BUFFER_BIT);
draw(&gui, xw.gwa.width, xw.gwa.height, &gui.out);
glXSwapBuffers(xw.dpy, xw.win);
/* Timing */
dt = timestamp() - started;
if (dt < DTIME) {
sleep_for(DTIME - dt);
dt = DTIME;
}
}
/* Cleanup */
delfont(gui.font);
glXMakeCurrent(xw.dpy, None, NULL);
glXDestroyContext(xw.dpy, xw.glc);
XDestroyWindow(xw.dpy, xw.win);
XCloseDisplay(xw.dpy);
return 0;
}