Skip to content

Instantly share code, notes, and snippets.

@YukinoHayakawa
Forked from durango/console.c
Created December 25, 2018 07:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save YukinoHayakawa/9b71fa81c24ca9daeedfbf2b3bff0540 to your computer and use it in GitHub Desktop.
Save YukinoHayakawa/9b71fa81c24ca9daeedfbf2b3bff0540 to your computer and use it in GitHub Desktop.
Basic gamepad controlled UI
/*
Copyright (c) 2016 Micha Mettke
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <assert.h>
#include <math.h>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL2/SDL.h>
/* macros */
#define MAX_VERTEX_MEMORY 512 * 1024
#define MAX_ELEMENT_MEMORY 128 * 1024
#define UNUSED(a) ((void)(a))
#define LEN(a) (sizeof(a)/sizeof(a)[0])
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) < (b) ? (b) : (a))
#include "zahnrad.h"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wfloat-equal"
#pragma clang diagnostic ignored "-Wbad-function-cast"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#pragma clang diagnostic ignored "-Wdeclaration-after-statement"
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic ignored "-Wbad-function-cast"
#pragma GCC diagnostic ignored "-Wcast-qual"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
#pragma GCC diagnostic ignored "-Wtype-limits"
#pragma GCC diagnostic ignored "-Wswitch-default"
#pragma GCC diagnostic ignored "-Wunused-function"
#elif _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4456)
#endif
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#ifdef __clang__
#pragma clang diagnostic pop
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop
#elif _MSC_VER
#pragma warning (pop)
#endif
/* ==============================================================
*
* Utility
*
* ===============================================================*/
static void
die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputs("\n", stderr);
exit(EXIT_FAILURE);
}
static char*
file_load(const char* path, size_t* siz)
{
char *buf;
FILE *fd = fopen(path, "rb");
if (!fd) die("Failed to open file: %s\n", path);
fseek(fd, 0, SEEK_END);
*siz = (size_t)ftell(fd);
fseek(fd, 0, SEEK_SET);
buf = (char*)calloc(*siz, 1);
fread(buf, *siz, 1, fd);
fclose(fd);
return buf;
}
static GLuint
image_load(const char *filename)
{
int x,y,n;
GLuint tex;
unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
if (!data) die("[SDL]: failed to load image: %s", filename);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, x, y, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
return tex;
}
struct device {
GLuint vbo, vao, ebo;
GLuint prog;
GLuint vert_shdr;
GLuint frag_shdr;
GLint attrib_pos;
GLint attrib_uv;
GLint attrib_col;
GLint uniform_tex;
GLint uniform_proj;
GLuint font_tex;
struct zr_draw_null_texture null;
struct zr_buffer cmds;
};
static void
device_init(struct device *dev)
{
GLint status;
static const GLchar *vertex_shader =
"#version 300 es\n"
"uniform mat4 ProjMtx;\n"
"in vec2 Position;\n"
"in vec2 TexCoord;\n"
"in vec4 Color;\n"
"out vec2 Frag_UV;\n"
"out vec4 Frag_Color;\n"
"void main() {\n"
" Frag_UV = TexCoord;\n"
" Frag_Color = Color;\n"
" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n"
"}\n";
static const GLchar *fragment_shader =
"#version 300 es\n"
"precision mediump float;\n"
"uniform sampler2D Texture;\n"
"in vec2 Frag_UV;\n"
"in vec4 Frag_Color;\n"
"out vec4 Out_Color;\n"
"void main(){\n"
" Out_Color = Frag_Color * texture(Texture, Frag_UV.st);\n"
"}\n";
dev->prog = glCreateProgram();
dev->vert_shdr = glCreateShader(GL_VERTEX_SHADER);
dev->frag_shdr = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(dev->vert_shdr, 1, &vertex_shader, 0);
glShaderSource(dev->frag_shdr, 1, &fragment_shader, 0);
glCompileShader(dev->vert_shdr);
glCompileShader(dev->frag_shdr);
glGetShaderiv(dev->vert_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glGetShaderiv(dev->frag_shdr, GL_COMPILE_STATUS, &status);
assert(status == GL_TRUE);
glAttachShader(dev->prog, dev->vert_shdr);
glAttachShader(dev->prog, dev->frag_shdr);
glLinkProgram(dev->prog);
glGetProgramiv(dev->prog, GL_LINK_STATUS, &status);
assert(status == GL_TRUE);
dev->uniform_tex = glGetUniformLocation(dev->prog, "Texture");
dev->uniform_proj = glGetUniformLocation(dev->prog, "ProjMtx");
dev->attrib_pos = glGetAttribLocation(dev->prog, "Position");
dev->attrib_uv = glGetAttribLocation(dev->prog, "TexCoord");
dev->attrib_col = glGetAttribLocation(dev->prog, "Color");
{
/* buffer setup */
GLsizei vs = sizeof(struct zr_draw_vertex);
size_t vp = offsetof(struct zr_draw_vertex, position);
size_t vt = offsetof(struct zr_draw_vertex, uv);
size_t vc = offsetof(struct zr_draw_vertex, col);
glGenBuffers(1, &dev->vbo);
glGenBuffers(1, &dev->ebo);
glGenVertexArrays(1, &dev->vao);
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glEnableVertexAttribArray((GLuint)dev->attrib_pos);
glEnableVertexAttribArray((GLuint)dev->attrib_uv);
glEnableVertexAttribArray((GLuint)dev->attrib_col);
glVertexAttribPointer((GLuint)dev->attrib_pos, 2, GL_FLOAT, GL_FALSE, vs, (void*)vp);
glVertexAttribPointer((GLuint)dev->attrib_uv, 2, GL_FLOAT, GL_FALSE, vs, (void*)vt);
glVertexAttribPointer((GLuint)dev->attrib_col, 4, GL_UNSIGNED_BYTE, GL_TRUE, vs, (void*)vc);
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
static struct zr_user_font
font_bake_and_upload(struct device *dev, struct zr_font *font,
const char *path, unsigned int font_height, const zr_rune *range)
{
int glyph_count;
int img_width, img_height;
struct zr_font_glyph *glyphes;
struct zr_baked_font baked_font;
struct zr_user_font user_font;
struct zr_recti custom;
memset(&baked_font, 0, sizeof(baked_font));
memset(&user_font, 0, sizeof(user_font));
memset(&custom, 0, sizeof(custom));
{
/* bake and upload font texture */
void *img, *tmp;
size_t ttf_size;
size_t tmp_size, img_size;
const char *custom_data = "....";
struct zr_font_config config;
char *ttf_blob = file_load(path, &ttf_size);
if (!ttf_blob)
die("[Font]: %s is not a file or cannot be found!\n", path);
/* setup font configuration */
memset(&config, 0, sizeof(config));
config.ttf_blob = ttf_blob;
config.ttf_size = ttf_size;
config.font = &baked_font;
config.coord_type = ZR_COORD_UV;
config.range = range;
config.pixel_snap = zr_false;
config.size = (float)font_height;
config.spacing = zr_vec2(0,0);
config.oversample_h = 1;
config.oversample_v = 1;
/* query needed amount of memory for the font baking process */
zr_font_bake_memory(&tmp_size, &glyph_count, &config, 1);
glyphes = (struct zr_font_glyph*)calloc(sizeof(struct zr_font_glyph), (size_t)glyph_count);
tmp = calloc(1, tmp_size);
/* pack all glyphes and return needed image width, height and memory size*/
custom.w = 2; custom.h = 2;
if (!zr_font_bake_pack(&img_size, &img_width,&img_height,&custom,tmp,tmp_size,&config, 1))
die("[Font]: failed to load font!\n");
/* bake all glyphes and custom white pixel into image */
img = calloc(1, img_size);
zr_font_bake(img, img_width, img_height, tmp, tmp_size, glyphes, glyph_count, &config, 1);
zr_font_bake_custom_data(img, img_width, img_height, custom, custom_data, 2, 2, '.', 'X');
{
/* convert alpha8 image into rgba8 image */
void *img_rgba = calloc(4, (size_t)(img_height * img_width));
zr_font_bake_convert(img_rgba, img_width, img_height, img);
free(img);
img = img_rgba;
}
{
/* upload baked font image */
glGenTextures(1, &dev->font_tex);
glBindTexture(GL_TEXTURE_2D, dev->font_tex);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)img_width, (GLsizei)img_height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, img);
glGenerateMipmap(GL_TEXTURE_2D);
}
free(ttf_blob);
free(tmp);
free(img);
}
/* default white pixel in a texture which is needed to draw primitives */
dev->null.texture.id = (int)dev->font_tex;
dev->null.uv = zr_vec2((custom.x + 0.5f)/(float)img_width,
(custom.y + 0.5f)/(float)img_height);
/* setup font with glyphes. IMPORTANT: the font only references the glyphes
this was done to have the possibility to have multible fonts with one
total glyph array. Not quite sure if it is a good thing since the
glyphes have to be freed as well. */
zr_font_init(font, (float)font_height, '?', glyphes, &baked_font, dev->null.texture);
user_font = zr_font_ref(font);
return user_font;
}
static void
device_shutdown(struct device *dev)
{
glDetachShader(dev->prog, dev->vert_shdr);
glDetachShader(dev->prog, dev->frag_shdr);
glDeleteShader(dev->vert_shdr);
glDeleteShader(dev->frag_shdr);
glDeleteProgram(dev->prog);
glDeleteTextures(1, &dev->font_tex);
glDeleteBuffers(1, &dev->vbo);
glDeleteBuffers(1, &dev->ebo);
glDeleteVertexArrays(1, &dev->vao);
}
static void
device_draw(struct device *dev, struct zr_context *ctx, int width, int height,
enum zr_anti_aliasing AA)
{
GLint last_prog, last_tex;
GLint last_ebo, last_vbo, last_vao;
GLfloat ortho[4][4] = {
{2.0f, 0.0f, 0.0f, 0.0f},
{0.0f,-2.0f, 0.0f, 0.0f},
{0.0f, 0.0f,-1.0f, 0.0f},
{-1.0f,1.0f, 0.0f, 1.0f},
};
ortho[0][0] /= (GLfloat)width;
ortho[1][1] /= (GLfloat)height;
/* save previous opengl state */
glGetIntegerv(GL_CURRENT_PROGRAM, &last_prog);
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_tex);
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_vao);
glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &last_ebo);
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &last_vbo);
/* setup global state */
glEnable(GL_BLEND);
glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glEnable(GL_SCISSOR_TEST);
glActiveTexture(GL_TEXTURE0);
/* setup program */
glUseProgram(dev->prog);
glUniform1i(dev->uniform_tex, 0);
glUniformMatrix4fv(dev->uniform_proj, 1, GL_FALSE, &ortho[0][0]);
{
/* convert from command queue into draw list and draw to screen */
const struct zr_draw_command *cmd;
void *vertices, *elements;
const zr_draw_index *offset = NULL;
/* allocate vertex and element buffer */
glBindVertexArray(dev->vao);
glBindBuffer(GL_ARRAY_BUFFER, dev->vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, dev->ebo);
glBufferData(GL_ARRAY_BUFFER, MAX_VERTEX_MEMORY, NULL, GL_STREAM_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, MAX_ELEMENT_MEMORY, NULL, GL_STREAM_DRAW);
/* load draw vertices & elements directly into vertex + element buffer */
vertices = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
elements = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
{
struct zr_buffer vbuf, ebuf;
/* fill converting configuration */
struct zr_convert_config config;
memset(&config, 0, sizeof(config));
config.global_alpha = 1.0f;
config.shape_AA = AA;
config.line_AA = AA;
config.circle_segment_count = 22;
config.line_thickness = 1.0f;
config.null = dev->null;
/* setup buffers to load vertices and elements */
zr_buffer_init_fixed(&vbuf, vertices, MAX_VERTEX_MEMORY);
zr_buffer_init_fixed(&ebuf, elements, MAX_ELEMENT_MEMORY);
zr_convert(ctx, &dev->cmds, &vbuf, &ebuf, &config);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
/* iterate over and execute each draw command */
zr_draw_foreach(cmd, ctx, &dev->cmds) {
if (!cmd->elem_count) continue;
glBindTexture(GL_TEXTURE_2D, (GLuint)cmd->texture.id);
glScissor((GLint)cmd->clip_rect.x,
height - (GLint)(cmd->clip_rect.y + cmd->clip_rect.h),
(GLint)cmd->clip_rect.w, (GLint)cmd->clip_rect.h);
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->elem_count, GL_UNSIGNED_SHORT, offset);
offset += cmd->elem_count;
}
zr_clear(ctx);
}
/* restore old state */
glUseProgram((GLuint)last_prog);
glBindTexture(GL_TEXTURE_2D, (GLuint)last_tex);
glBindBuffer(GL_ARRAY_BUFFER, (GLuint)last_vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)last_ebo);
glBindVertexArray((GLuint)last_vao);
glDisable(GL_SCISSOR_TEST);
}
static void
input_key(struct zr_context *ctx, SDL_Event *evt, int down)
{
const Uint8* state = SDL_GetKeyboardState(NULL);
SDL_Keycode sym = evt->key.keysym.sym;
if (sym == SDLK_RSHIFT || sym == SDLK_LSHIFT)
zr_input_key(ctx, ZR_KEY_SHIFT, down);
else if (sym == SDLK_DELETE)
zr_input_key(ctx, ZR_KEY_DEL, down);
else if (sym == SDLK_RETURN)
zr_input_key(ctx, ZR_KEY_ENTER, down);
else if (sym == SDLK_TAB)
zr_input_key(ctx, ZR_KEY_TAB, down);
else if (sym == SDLK_BACKSPACE)
zr_input_key(ctx, ZR_KEY_BACKSPACE, down);
else if (sym == SDLK_UP)
zr_input_key(ctx, ZR_KEY_UP, down);
else if (sym == SDLK_DOWN)
zr_input_key(ctx, ZR_KEY_DOWN, down);
else if (sym == SDLK_LEFT)
zr_input_key(ctx, ZR_KEY_LEFT, down);
else if (sym == SDLK_RIGHT)
zr_input_key(ctx, ZR_KEY_RIGHT, down);
else if (sym == SDLK_c)
zr_input_key(ctx, ZR_KEY_COPY, down && state[SDL_SCANCODE_LCTRL]);
else if (sym == SDLK_v)
zr_input_key(ctx, ZR_KEY_PASTE, down && state[SDL_SCANCODE_LCTRL]);
else if (sym == SDLK_x)
zr_input_key(ctx, ZR_KEY_CUT, down && state[SDL_SCANCODE_LCTRL]);
}
static void
input_motion(struct zr_context *ctx, SDL_Event *evt)
{
const int x = evt->motion.x;
const int y = evt->motion.y;
zr_input_motion(ctx, x, y);
}
static void
input_button(struct zr_context *ctx, SDL_Event *evt, int down)
{
const int x = evt->button.x;
const int y = evt->button.y;
if (evt->button.button == SDL_BUTTON_LEFT)
zr_input_button(ctx, ZR_BUTTON_LEFT, x, y, down);
if (evt->button.button == SDL_BUTTON_MIDDLE)
zr_input_button(ctx, ZR_BUTTON_MIDDLE, x, y, down);
if (evt->button.button == SDL_BUTTON_RIGHT)
zr_input_button(ctx, ZR_BUTTON_RIGHT, x, y, down);
}
static void
input_text(struct zr_context *ctx, SDL_Event *evt)
{
zr_glyph glyph;
memcpy(glyph, evt->text.text, ZR_UTF_SIZE);
zr_input_glyph(ctx, glyph);
}
static void
resize(SDL_Event *evt)
{
if (evt->window.event != SDL_WINDOWEVENT_RESIZED) return;
glViewport(0, 0, evt->window.data1, evt->window.data2);
}
static void* mem_alloc(zr_handle unused, size_t size)
{UNUSED(unused); return calloc(1, size);}
static void mem_free(zr_handle unused, void *ptr)
{UNUSED(unused); free(ptr);}
static int
ui_selector(struct zr_context *ctx, const char *title, int *selected, const char *items[],
int max, int active)
{
struct zr_vec2 item_padding;
struct zr_rect bounds, label, content, tri, sel;
struct zr_window *win;
struct zr_panel *layout;
struct zr_command_buffer *out;
struct zr_color col;
struct zr_vec2 result[3];
zr_size text_len, text_width;
ZR_ASSERT(ctx);
ZR_ASSERT(ctx->current);
if (!ctx || !ctx->current)
return 0;
win = ctx->current;
layout = zr_window_get_panel(ctx);
ZR_ASSERT(layout);
out = zr_window_get_canvas(ctx);
if (!zr_widget(&bounds, ctx))
return 0;
item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING);
bounds.x += item_padding.x;
bounds.y += item_padding.y;
bounds.w -= 2 * item_padding.x;
bounds.h -= 2 * item_padding.y;
label.h = bounds.h;
label.w = bounds.w / 2.0f;
label.x = bounds.x + item_padding.x;
label.y = bounds.y + label.h/2.0f - (float)ctx->style.font.height/2.0f;
content.x = bounds.x + bounds.w/2.0f;
content.y = bounds.y;
content.w = bounds.w / 2.0f;
content.h = bounds.h;
if (active) zr_draw_rect(out, bounds, 0, zr_rgba(220, 220, 220, 135));
text_len = strlen(title);
col = (active) ? zr_rgba(0, 0, 0, 255): zr_rgba(220,220,220,220);
zr_draw_text(out, label, title, text_len, &ctx->style.font, zr_rgba(0,0,0,0), col);
if (zr_input_is_key_pressed(&ctx->input, ZR_KEY_RIGHT) && active)
*selected = MIN(*selected+1, max-1);
else if (zr_input_is_key_pressed(&ctx->input, ZR_KEY_LEFT) && active)
*selected = MAX(0, *selected-1);
tri.h = ctx->style.font.height - 2 * item_padding.y;
tri.w = tri.h/2.0f;
tri.x = content.x + item_padding.x;
tri.y = content.y + content.h/2 - tri.h/2.0f;
sel.x = tri.x + item_padding.x;
sel.y = tri.y;
sel.h = content.h;
if (*selected > 0) {
zr_triangle_from_direction(result, tri, 0, 0, ZR_LEFT);
zr_draw_triangle(out, result[0].x, result[0].y, result[1].x, result[1].y,
result[2].x, result[2].y, (active) ? zr_rgba(0, 0, 0, 255):
zr_rgba(100, 100, 100, 150));
}
tri.x = content.x + (content.w - item_padding.x) - tri.w;
sel.w = tri.x - sel.x;
if (*selected < max-1) {
zr_triangle_from_direction(result, tri, 0, 0, ZR_RIGHT);
zr_draw_triangle(out, result[0].x, result[0].y, result[1].x, result[1].y,
result[2].x, result[2].y, (active) ? zr_rgba(0, 0, 0, 255):
zr_rgba(100, 100, 100, 150));
}
text_width = ctx->style.font.width(ctx->style.font.userdata,
ctx->style.font.height, items[*selected], strlen(items[*selected]));
label.w = MAX(1, (float)text_width);
label.x = (sel.x + (sel.w - label.w) / 2);
label.x = MAX(sel.x, label.x);
label.w = MIN(sel.x + sel.w, label.x + label.w);
if (label.w >= label.x) label.w -= label.x;
zr_draw_text(out, label, items[*selected], strlen(items[*selected]),
&ctx->style.font, zr_rgba(0,0,0,0), col);
return 0;
}
static void
ui_slider(struct zr_context *ctx, const char *title, int *value, int max, int active)
{
struct zr_vec2 item_padding;
struct zr_rect bounds, label, content, bar, cursor, tri;
struct zr_window *win;
struct zr_panel *layout;
struct zr_command_buffer *out;
struct zr_color col;
zr_size text_len, text_width;
float prog_scale = (float)*value / (float)max;
struct zr_vec2 result[3];
ZR_ASSERT(ctx);
ZR_ASSERT(ctx->current);
if (!ctx || !ctx->current)
return;
win = ctx->current;
layout = zr_window_get_panel(ctx);
ZR_ASSERT(layout);
out = zr_window_get_canvas(ctx);
if (!zr_widget(&bounds, ctx))
return;
item_padding = zr_get_property(ctx, ZR_PROPERTY_ITEM_PADDING);
bounds.x += item_padding.x;
bounds.y += item_padding.y;
bounds.w -= 2 * item_padding.x;
bounds.h -= 2 * item_padding.y;
label.h = bounds.h;
label.w = bounds.w / 2.0f;
label.x = bounds.x + item_padding.x;
label.y = bounds.y + label.h/2.0f - (float)ctx->style.font.height/2.0f;
content.x = bounds.x + bounds.w/2.0f;
content.y = bounds.y;
content.w = bounds.w / 2.0f;
content.h = bounds.h;
if (active) zr_draw_rect(out, bounds, 0, zr_rgba(220, 220, 220, 135));
text_len = strlen(title);
col = (active) ? zr_rgba(0, 0, 0, 255): zr_rgba(220,220,220,220);
zr_draw_text(out, label, title, text_len, &ctx->style.font, zr_rgba(0,0,0,0), col);
if (zr_input_is_key_pressed(&ctx->input, ZR_KEY_LEFT) && active)
*value = MAX(0, *value - 10);
if (zr_input_is_key_pressed(&ctx->input, ZR_KEY_RIGHT) && active)
*value = MIN(*value + 10, max);
tri.h = ctx->style.font.height - 2 * item_padding.y;
tri.w = tri.h/2.0f;
tri.x = content.x + item_padding.x;
tri.y = content.y + content.h/2 - tri.h/2.0f;
bar.x = tri.x + 4 * item_padding.x + tri.w;
bar.h = tri.h / 4.0f;
bar.y = tri.y + tri.h/2.0f - bar.h/2.0f;
if (*value > 0) {
zr_triangle_from_direction(result, tri, 0, 0, ZR_LEFT);
zr_draw_triangle(out, result[0].x, result[0].y, result[1].x, result[1].y,
result[2].x, result[2].y, (active) ? zr_rgba(0, 0, 0, 255):
zr_rgba(100, 100, 100, 150));
}
tri.x = content.x + (content.w - item_padding.x) - tri.w;
bar.w = (tri.x - bar.x) - 4 * item_padding.x;
if (*value < max) {
zr_triangle_from_direction(result, tri, 0, 0, ZR_RIGHT);
zr_draw_triangle(out, result[0].x, result[0].y, result[1].x, result[1].y,
result[2].x, result[2].y, (active) ? zr_rgba(0, 0, 0, 255):
zr_rgba(100, 100, 100, 150));
}
bar.w = (bar.w - tri.h/2.0f);
if (active) {
zr_draw_rect(out, bar, 0, zr_rgba(0, 0, 0, 135));
bar.w = bar.w * prog_scale;
bar.y = tri.y; bar.x = bar.x + bar.w; bar.w = tri.h; bar.h = tri.h;
zr_draw_circle(out, bar, zr_rgba(220, 220, 220, 255));
} else {
zr_draw_rect(out, bar, 0, zr_rgba(220, 220, 220, 135));
bar.w = bar.w * prog_scale;
bar.y = tri.y; bar.x = bar.x + bar.w; bar.w = tri.h; bar.h = tri.h;
zr_draw_circle(out, bar, zr_rgba(190, 190, 190, 255));
}
}
int
main(int argc, char *argv[])
{
/* Platform */
const char *font_path;
SDL_Window *win;
SDL_GLContext glContext;
int win_width, win_height;
int running = 1;
GLuint image;
SDL_GameController *controller = 0;
/* GUI */
struct device device;
struct zr_context ctx;
struct zr_font font;
font_path = "half_life.ttf";
/* SDL */
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_GAMECONTROLLER);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
win = SDL_CreateWindow("Demo",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
1600, 900, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
glContext = SDL_GL_CreateContext(win);
SDL_GetWindowSize(win, &win_width, &win_height);
{
int i = 0;
int max_joysticks = SDL_NumJoysticks();
for (i = 0; i < max_joysticks; ++i) {
if (!SDL_IsGameController(i))
continue;
controller = SDL_GameControllerOpen(i);
break;
}
}
/* OpenGL */
glewExperimental = 1;
if (glewInit() != GLEW_OK)
die("Failed to setup GLEW\n");
{
/* GUI */
struct zr_user_font usrfnt;
struct zr_allocator alloc;
alloc.userdata.ptr = NULL;
alloc.alloc = mem_alloc;
alloc.free = mem_free;
zr_buffer_init(&device.cmds, &alloc, 1024);
usrfnt = font_bake_and_upload(&device, &font, font_path, 40,
zr_font_default_glyph_ranges());
zr_init(&ctx, &alloc, &usrfnt);
}
device_init(&device);
image = image_load("background.png");
ctx.style.rounding[ZR_ROUNDING_SCROLLBAR] = 0;
ctx.style.properties[ZR_PROPERTY_SCROLLBAR_SIZE] = zr_vec2(10,10);
ctx.style.colors[ZR_COLOR_TEXT] = zr_rgba(210, 210, 210, 255);
ctx.style.colors[ZR_COLOR_TEXT_HOVERING] = zr_rgba(195, 195, 195, 255);
ctx.style.colors[ZR_COLOR_TEXT_ACTIVE] = zr_rgba(200, 200, 200, 255);
ctx.style.colors[ZR_COLOR_WINDOW] = zr_rgba(0, 0, 0, 200);
ctx.style.colors[ZR_COLOR_HEADER] = zr_rgba(0, 0, 0, 0);
while (running) {
/* Input */
SDL_Event evt;
zr_input_begin(&ctx);
while (SDL_PollEvent(&evt)) {
if (evt.type == SDL_WINDOWEVENT) resize(&evt);
else if (evt.type == SDL_QUIT) goto cleanup;
else if (evt.type == SDL_KEYUP)
input_key(&ctx, &evt, zr_false);
else if (evt.type == SDL_KEYDOWN)
input_key(&ctx, &evt, zr_true);
else if (evt.type == SDL_MOUSEBUTTONDOWN)
input_button(&ctx, &evt, zr_true);
else if (evt.type == SDL_MOUSEBUTTONUP)
input_button(&ctx, &evt, zr_false);
else if (evt.type == SDL_MOUSEMOTION)
input_motion(&ctx, &evt);
else if (evt.type == SDL_TEXTINPUT)
input_text(&ctx, &evt);
else if (evt.type == SDL_MOUSEWHEEL)
zr_input_scroll(&ctx,(float)evt.wheel.y);
else if (evt.type == SDL_CONTROLLERBUTTONDOWN ||
evt.type == SDL_CONTROLLERBUTTONUP)
{
if (evt.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_UP)
zr_input_key(&ctx, ZR_KEY_UP, evt.cbutton.state == SDL_PRESSED);
else if (evt.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN)
zr_input_key(&ctx, ZR_KEY_DOWN, evt.cbutton.state == SDL_PRESSED);
else if (evt.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT)
zr_input_key(&ctx, ZR_KEY_LEFT, evt.cbutton.state == SDL_PRESSED);
else if (evt.cbutton.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT)
zr_input_key(&ctx, ZR_KEY_RIGHT, evt.cbutton.state == SDL_PRESSED);
}
}
zr_input_end(&ctx);
/* GUI */
SDL_GetWindowSize(win, &win_width, &win_height);
{
enum widget_id {
WINDOW_MODE = 0,
MODEL_DETAIL,
TEXTURES,
SHADOWS,
LIGHTNING,
EFFECTS,
CONSOLE,
BRIGHTNESS,
VOLUME,
WIDGET_MAX
};
enum display_settings {WINDOWED, FULLSCREEN};
enum detail_settings {LOW, MEDIUM, HIGH, EXTRA_HIGH};
enum state_settings {OFF, ON};
struct zr_panel layout;
const char *display[] = {"Windowed", "Fullscreen"};
const char *state[] = {"Off", "On"};
const char *detail[] = {"Low", "Medium", "High", "Extra High"};
static int window_mode = FULLSCREEN;
static int model_detail = HIGH;
static int texture_detail = EXTRA_HIGH;
static int shadow_detail = HIGH;
static int lighning_detail = LOW;
static int effects_detail = MEDIUM;
static int show_console = ON;
static int brightness = 90;
static int volume = 30;
static int active = WINDOW_MODE;
const struct zr_input *in = &ctx.input;
if (zr_input_is_key_pressed(in, ZR_KEY_UP))
active = MAX(0, active-1);
if (zr_input_is_key_pressed(in, ZR_KEY_DOWN))
active = MIN(active+1, WIDGET_MAX-1);
ctx.style.font.height = 50;
zr_begin(&ctx, &layout, "GAME SETTINGS", zr_rect(200, 150, 1200, 700),ZR_WINDOW_TITLE);
ctx.style.font.height = 40;
zr_layout_row_dynamic(&ctx, 50, 1);
ui_selector(&ctx, "Windowed Mode", &window_mode, display, LEN(display), WINDOW_MODE == active);
ui_selector(&ctx, "Model Detail", &model_detail, detail, LEN(detail), MODEL_DETAIL == active);
ui_selector(&ctx, "Textures", &texture_detail, detail, LEN(detail), TEXTURES == active);
ui_selector(&ctx, "Shadows", &shadow_detail, detail, LEN(detail), SHADOWS == active);
ui_selector(&ctx, "Lighting", &lighning_detail, detail, LEN(detail), LIGHTNING == active);
ui_selector(&ctx, "Effects", &effects_detail, detail, LEN(detail), EFFECTS == active);
ui_selector(&ctx, "Console", &show_console, state, LEN(state), CONSOLE == active);
ui_slider(&ctx, "Brightness", &brightness, 100, BRIGHTNESS == active);
ui_slider(&ctx, "Volume", &volume, 100, VOLUME == active);
zr_end(&ctx);
}
glViewport(0, 0, win_width, win_height);
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
/* draw background image */
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, image);
glColor3f(1.0f, 1.0f, 1.0f);
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex2f(-1.0f, 1.0f);
glTexCoord2f(1, 0); glVertex2f( 1.0f, 1.0f);
glTexCoord2f(1, 1); glVertex2f( 1.0f,-1.0f);
glTexCoord2f(0, 1); glVertex2f(-1.0f,-1.0f);
glEnd();
glDisable(GL_TEXTURE_2D);
/* draw gui */
device_draw(&device, &ctx, win_width, win_height, ZR_ANTI_ALIASING_ON);
SDL_GL_SwapWindow(win);
}
cleanup:
if (controller)
SDL_GameControllerClose(controller);
free(font.glyphs);
zr_free(&ctx);
zr_buffer_free(&device.cmds);
device_shutdown(&device);
SDL_GL_DeleteContext(glContext);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment