diff --git a/Makefile b/Makefile index 5670541..716fe08 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BIN=cudoku CC=gcc -CFLAGS=-Wall -Wextra -pedantic -g `pkg-config --cflags freetype2` -I3rdparty/glad/include -I3rdparty/stb -I3rdparty/fmod/include -OBJ=main.o x11.o cudoku.o shader.o font.o helper.o audio.o timer.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o +CFLAGS=-Wall -Wextra -O3 -pedantic -g `pkg-config --cflags freetype2` -I3rdparty/glad/include -I3rdparty/stb -I3rdparty/fmod/include +OBJ=main.o x11.o cudoku.o shader.o font.o helper.o audio.o timer.o ui.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o LDFLAGS=`pkg-config --libs x11 freetype2` -lm -L3rdparty/fmod/lib -Wl,-rpath=3rdparty/fmod/lib -lfmod DEPS=3rdparty/glad/include/glad/gl.h 3rdparty/glad/include/glad/glx.h 3rdparty/stb/stb_image.h 3rdparty/fmod/include/fmod.h diff --git a/audio.c b/audio.c index 3890628..3e4c9ba 100644 --- a/audio.c +++ b/audio.c @@ -59,7 +59,7 @@ void audio_play_win() { }; } -void audio_cleanup() { +void audio_close() { FMOD_Sound_Release(win_sound); FMOD_Sound_Release(scribble_sound); FMOD_ChannelGroup_Release(sfx_channel_group); diff --git a/audio.h b/audio.h index 12fe409..c0ac5de 100644 --- a/audio.h +++ b/audio.h @@ -1,5 +1,7 @@ +#pragma once + int audio_init(); -void audio_cleanup(); +void audio_close(); void audio_update(); void audio_play_scribble(); void audio_play_win(); diff --git a/cudoku.c b/cudoku.c index 0a958bb..8bfb039 100644 --- a/cudoku.c +++ b/cudoku.c @@ -8,9 +8,9 @@ #include "3rdparty/stb/stb_image.h" #include "cudoku.h" -#include "font.h" #include "shader.h" #include "audio.h" +#include "ui.h" #define HELP_TEXT_SIZE 13 @@ -47,6 +47,32 @@ static const char *help_texts[HELP_TEXT_SIZE] = { "Ctrl+Q/Esc - Quit" }; +void draw_win() { + Size window = zephr_get_window_size(); + Color bg_color = {233.f, 84.f, 32.f, 200.f}; + Color text_color = {237.f, 225.f, 215.f, 255.f}; + + UIConstraints constraints; + /* set_x_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); */ + /* set_y_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); */ + /* set_width_constraint(&constraints, window.width, UI_CONSTRAINT_FIXED); */ + /* set_height_constraint(&constraints, window.height, UI_CONSTRAINT_FIXED); */ + + Alignment align = ALIGN_TOP_RIGHT; + float scale = 1.0f; + + Sizef text_size = calculate_text_size(win_text, scale); + + set_x_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&constraints, 0, UI_CONSTRAINT_FIXED); + set_width_constraint(&constraints, text_size.width, UI_CONSTRAINT_FIXED); + set_height_constraint(&constraints, text_size.height, UI_CONSTRAINT_FIXED); + + draw_quad(constraints, &bg_color, align); + + draw_text(win_text, scale, (Vec2f){0.0, 0.0}, &text_color, align); +} + void set_scale_factor(int width, int height, float *x, float *y) { if (width > height) { float ratio = (float)height / (float)width; @@ -134,7 +160,7 @@ void draw_numbers(Shader shader, unsigned int vao, unsigned int vbo, float *tran for (int i = 0; i < 9; i++) { for (int j = 0; j < 9; j++) { if (board[i][j].value != 0) { - draw_number(shader, board[i][j], i, j, 5.0f, vao, vbo, transform); + /* draw_number(shader, board[i][j], i, j, 5.0f, vao, vbo, transform); */ } } } @@ -180,12 +206,30 @@ void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, u float time_elapsed = game->win_time - game->timer.time; snprintf(time_text, 64, "Solved in: %02dm%02ds", (int)time_elapsed / 60, (int)time_elapsed % 60); - Size win_text_size = calculate_text_size(win_text, 1.0f); - Size time_text_size = calculate_text_size(time_text, 1.0f); - draw_text_center(font_shader, win_text, win_text_size, 1.5f, font_vao, font_vbo, transform, 0.0, 75.0); - draw_text_center(font_shader, time_text, time_text_size, 0.8f, font_vao, font_vbo, transform, 0.0, -75.0); + /* Size win_text_size = calculate_text_size(win_text, 1.0f); */ + /* Size time_text_size = calculate_text_size(time_text, 1.0f); */ + /* draw_text_center(font_shader, win_text, win_text_size, 1.5f, font_vao, font_vbo, transform, 0.0, 75.0); */ + /* draw_text_center(font_shader, time_text, time_text_size, 0.8f, font_vao, font_vbo, transform, 0.0, -75.0); */ } +/* void draw_pause_overlay(Cudoku *game) { */ +/* timer_pause(&game->timer); */ + +/* // */ +/* // this is my basic idea of drawing ui elements */ +/* // */ +/* // examples of drawing shapes */ +/* draw_quad(beginning_x, beginning_y, win_width, win_height); */ +/* draw_circle(); */ +/* draw_triangle(); */ + +/* // example of drawing text */ +/* draw_text_at(pause_text, font_scale, posx, posy, color); */ + +/* // TODO: rotations, translation, scaling */ +/* // TODO: buttons and other elements */ +/* } */ + void prepare_selection_box(unsigned int *vao, unsigned int *vbo) { glGenVertexArrays(1, &(*vao)); glGenBuffers(1, &(*vbo)); @@ -625,9 +669,9 @@ void draw_help_overlay(Shader overlay_shader, Shader font_shader, unsigned int o int overlay_width = 0; for (int i = 0; i < HELP_TEXT_SIZE; i++) { - Size text_size = calculate_text_size(help_texts[i], text_scale); - overlay_height += text_size.height + text_padding; - overlay_width = max(overlay_width, text_size.width + text_padding * 2); + /* Size text_size = calculate_text_size(help_texts[i], text_scale); */ + /* overlay_height += text_size.height + text_padding; */ + /* overlay_width = max(overlay_width, text_size.width + text_padding * 2); */ } Size size = {.width = overlay_width, .height = overlay_height + text_padding * 2}; Vec2 pos = { @@ -643,30 +687,30 @@ void draw_help_overlay(Shader overlay_shader, Shader font_shader, unsigned int o draw_ui_element_at(overlay_shader, overlay_vao, overlay_vbo, projection, pos, size, color); for (int i = 0; i < HELP_TEXT_SIZE; i++) { - Size text_size = calculate_text_size(help_texts[i], text_scale); - Vec2 text_pos = { - .x = text_padding, - .y = window_height - total_help_texts_height - (text_padding * 2) - }; - total_help_texts_height += text_size.height + text_padding; - draw_text_at(font_shader, help_texts[i], text_pos, text_scale, font_vao, font_vbo, (float *)projection->m, NULL); + /* Size text_size = calculate_text_size(help_texts[i], text_scale); */ + /* Vec2 text_pos = { */ + /* .x = text_padding, */ + /* .y = window_height - total_help_texts_height - (text_padding * 2) */ + /* }; */ + /* total_help_texts_height += text_size.height + text_padding; */ + /* draw_text_at(font_shader, help_texts[i], text_pos, text_scale, font_vao, font_vbo, (float *)projection->m, NULL); */ } if (timer->is_running) { char closing_in_text[128]; - snprintf(closing_in_text, sizeof(closing_in_text), "Hiding in %ds", (int)timer_remaining(timer) + 1); - Size closing_in_text_size = calculate_text_size(closing_in_text, text_scale / 2.0); - Vec2 closing_in_text_pos = { - .x = overlay_width - closing_in_text_size.width, - .y = window_height - closing_in_text_size.height - text_padding, - }; - Color closing_in_text_color = { - .r = 255.0f, - .g = 150.0f, - .b = 0.0f, - .a = 1.0f, - }; - draw_text_at(font_shader, closing_in_text, closing_in_text_pos, text_scale / 2.0, font_vao, font_vbo, (float *)projection->m, &closing_in_text_color); + /* snprintf(closing_in_text, sizeof(closing_in_text), "Hiding in %ds", (int)timer_remaining(timer) + 1); */ + /* Size closing_in_text_size = calculate_text_size(closing_in_text, text_scale / 2.0); */ + /* Vec2 closing_in_text_pos = { */ + /* .x = overlay_width - closing_in_text_size.width, */ + /* .y = window_height - closing_in_text_size.height - text_padding, */ + /* }; */ + /* Color closing_in_text_color = { */ + /* .r = 255.0f, */ + /* .g = 150.0f, */ + /* .b = 0.0f, */ + /* .a = 1.0f, */ + /* }; */ + /* draw_text_at(font_shader, closing_in_text, closing_in_text_pos, text_scale / 2.0, font_vao, font_vbo, (float *)projection->m, &closing_in_text_color); */ } } @@ -679,19 +723,19 @@ void draw_timer(Shader shader, unsigned int vao, unsigned int vbo, float *projec int minutes = elapsed / 60; int seconds = (int)elapsed % 60; - snprintf(timer_text, sizeof(timer_text), "Time: %02d:%02d", minutes, seconds); - float text_scale = 0.35f; - Size text_size = calculate_text_size(timer_text, text_scale); - Vec2 text_pos = { - .x = win_width - text_size.width - 10, - .y = 10, - }; - Color text_color = { - .r = 66.0f, - .g = 92.0f, - .b = 124.0f, - .a = 1.0f, - }; + /* snprintf(timer_text, sizeof(timer_text), "Time: %02d:%02d", minutes, seconds); */ + /* float text_scale = 0.35f; */ + /* Size text_size = calculate_text_size(timer_text, text_scale); */ + /* Vec2 text_pos = { */ + /* .x = win_width - text_size.width - 10, */ + /* .y = 10, */ + /* }; */ + /* Color text_color = { */ + /* .r = 66.0f, */ + /* .g = 92.0f, */ + /* .b = 124.0f, */ + /* .a = 1.0f, */ + /* }; */ - draw_text_at(shader, timer_text, text_pos, text_scale, vao, vbo, projection, &text_color); + /* draw_text_at(shader, timer_text, text_pos, text_scale, vao, vbo, projection, &text_color); */ } diff --git a/cudoku.h b/cudoku.h index 33128a8..87710f4 100644 --- a/cudoku.h +++ b/cudoku.h @@ -5,11 +5,7 @@ #include "helper.h" #include "shader.h" #include "timer.h" - -typedef struct Vec2 { - int x; - int y; -} Vec2; +#include "ui.h" typedef struct Cell { int value; @@ -29,13 +25,6 @@ typedef struct Cudoku { float win_time; } Cudoku; -typedef struct Color { - float r; - float g; - float b; - float a; -} Color; - void set_scale_factor(int width, int height, float *x, float *y); unsigned int prepare_bg(bool use_texture, unsigned int *texture); void draw_bg_grid_shader(Shader shader, unsigned int vao, float* transform, float resolution); @@ -66,3 +55,4 @@ void draw_help_overlay( Timer *timer ); void draw_timer(Shader shader, unsigned int vao, unsigned int vbo, float *projection, Timer *timer, int win_width); +void draw_win(); diff --git a/font.c b/font.c index 5d70667..383113c 100644 --- a/font.c +++ b/font.c @@ -1,248 +1,155 @@ -#include -#include FT_FREETYPE_H -#include -#include +/* #include */ -#include "font.h" -#include "shader.h" +/* #include "font.h" */ +/* #include "shader.h" */ -Character characters[128]; +/* void draw_number(Shader shader, Cell cell, int row, int column, float scale, unsigned int vao, unsigned int vbo, float *transform) { */ +/* use_shader(shader); */ +/* if (cell.is_locked) { */ +/* set_vec3f(shader, "textColor", 44.f / 255.f, 54.f / 255.f, 57.f / 255.f); */ +/* } else { */ +/* set_vec3f(shader, "textColor", 110.f / 255.f, 133.f / 255.f, 183.f / 255.f); */ +/* } */ +/* set_mat4f(shader, "transform", transform); */ +/* glActiveTexture(GL_TEXTURE0); */ +/* glBindVertexArray(vao); */ -int init_fonts(const char *font_path) { - FT_Library ft; - if (FT_Init_FreeType(&ft)) { - return -1; - } +/* // get ascii value of cell value and use it to index into characters */ +/* Character ch = characters['0' + cell.value]; */ - FT_Face face; - if (FT_New_Face(ft, font_path, 0, &face)) { - return -2; - } +/* float w = ch.size.width; */ +/* float h = ch.size.height; */ +/* float ratio = w / h; */ - if ((face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { - printf("[INFO] Got a variable font\n"); - FT_MM_Var *mm; - FT_Get_MM_Var(face, &mm); +/* // draw the glyph over the entire board and then scale it down to the size */ +/* // of a single cell */ +/* float vert_h = 1.0; */ +/* float vert_w = vert_h * ratio; */ +/* // get size of a single cell in opengl coords (-1, 1) */ +/* float single_cell = (1.f/9.f) * 2; */ +/* // we minus 4 since we start at column and row 4 (0-indexed) */ +/* float offset_x = single_cell * (column - 4); */ +/* float offset_y = single_cell * (row - 4); */ - FT_Set_Var_Design_Coordinates(face, mm->num_namedstyles, mm->namedstyle[mm->num_namedstyles - 4].coords); +/* // texture coords are y-flipped */ +/* float vertices[6][4] = { */ +/* // top left tri */ +/* { ((-(vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 0.0f, 1.0f }, */ +/* { ((-(vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 0.0f, 0.0f }, */ +/* { (( (vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 1.0f, 0.0f }, */ - FT_Done_MM_Var(ft, mm); - } +/* // bottom right tri */ +/* { ((-(vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 0.0f, 1.0f }, */ +/* { (( (vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 1.0f, 0.0f }, */ +/* { (( (vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 1.0f, 1.0f } */ +/* }; */ - FT_Set_Pixel_Sizes(face, 0, 128); +/* glBindTexture(GL_TEXTURE_2D, ch.texture_id); */ - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); +/* glBindBuffer(GL_ARRAY_BUFFER, vbo); */ +/* glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); */ - for (int c = 0; c < 128; c++) { - if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { - printf("[ERROR]: failed to load glyph for char '%c'\n", c); - } +/* glBindBuffer(GL_ARRAY_BUFFER, 0); */ - unsigned int texture; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, - face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, - face->glyph->bitmap.buffer); +/* glDrawArrays(GL_TRIANGLES, 0, 6); */ - 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_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +/* glBindVertexArray(0); */ +/* glBindTexture(GL_TEXTURE_2D, 0); */ +/* } */ - characters[c].texture_id = texture; - characters[c].advance = face->glyph->advance.x; - characters[c].size = (Size){ .width = face->glyph->bitmap.width, .height = face->glyph->bitmap.rows }; - characters[c].bearing = (Size){ .width = face->glyph->bitmap_left, .height = face->glyph->bitmap_top }; - } +/* void draw_text_center(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform, float offset_x, float offset_y) { */ +/* use_shader(shader); */ +/* set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); */ +/* set_mat4f(shader, "transform", transform); */ +/* glActiveTexture(GL_TEXTURE0); */ +/* glBindVertexArray(vao); */ - glBindTexture(GL_TEXTURE_2D, 0); +/* int c = 0; */ +/* int x = 0; */ +/* while (text[c] != '\0') { */ +/* Character ch = characters[(int)text[c]]; */ - FT_Done_Face(face); - FT_Done_FreeType(ft); +/* float xpos = ((x + ch.bearing.width * scale + offset_x) / 900.f) - ((text_size.width / 2.f) * scale / 900.f); */ +/* float ypos = (((ch.bearing.height - ch.size.height + offset_y) * scale) / 900.f) - ((text_size.height / 2.f) * scale / 900.f); */ - return 0; -} +/* float w = (ch.size.width * scale / 900.f); */ +/* float h = (ch.size.height * scale / 900.f); */ -void prepare_font(unsigned int *vao, unsigned int *vbo) { - glGenVertexArrays(1, &(*vao)); - glGenBuffers(1, &(*vbo)); +/* float vertices[6][4] = { */ +/* // top left tri */ +/* { xpos, ypos + h, 0.0f, 0.0f }, */ +/* { xpos, ypos, 0.0f, 1.0f }, */ +/* { xpos + w, ypos, 1.0f, 1.0f }, */ - glBindVertexArray(*vao); - glBindBuffer(GL_ARRAY_BUFFER, *vbo); +/* // bottom right tri */ +/* { xpos, ypos + h, 0.0f, 0.0f }, */ +/* { xpos + w, ypos, 1.0f, 1.0f }, */ +/* { xpos + w, ypos + h, 1.0f, 0.0f } */ +/* }; */ - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6*4, NULL, GL_DYNAMIC_DRAW); +/* glBindTexture(GL_TEXTURE_2D, ch.texture_id); */ - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0); +/* glBindBuffer(GL_ARRAY_BUFFER, vbo); */ +/* glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); */ +/* glBindBuffer(GL_ARRAY_BUFFER, 0); */ - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); -} +/* glDrawArrays(GL_TRIANGLES, 0, 6); */ -Size calculate_text_size(const char *text, float scale) { - Size size = { .width = 0, .height = 0 }; - float w = 0; - float h = 0; - for (uint i = 0; i < strlen(text); i++) { - Character ch = characters[(uint)text[i]]; - w += ch.advance / 64.0; - if (ch.size.height > h) { - h = ch.size.height; - } - } - size.width = w * scale; - size.height = h * scale; - return size; -} +/* x += (ch.advance >> 6) * scale; */ +/* c++; */ +/* } */ -void draw_number(Shader shader, Cell cell, int row, int column, float scale, unsigned int vao, unsigned int vbo, float *transform) { - use_shader(shader); - if (cell.is_locked) { - set_vec3f(shader, "textColor", 44.f / 255.f, 54.f / 255.f, 57.f / 255.f); - } else { - set_vec3f(shader, "textColor", 110.f / 255.f, 133.f / 255.f, 183.f / 255.f); - } - set_mat4f(shader, "transform", transform); - glActiveTexture(GL_TEXTURE0); - glBindVertexArray(vao); +/* glBindVertexArray(0); */ +/* glBindTexture(GL_TEXTURE_2D, 0); */ +/* } */ - // get ascii value of cell value and use it to index into characters - Character ch = characters['0' + cell.value]; +/* Color default_text_color = { 200.f / 255.f, 200.f / 255.f, 200.f / 255.f, 1.f }; */ - float w = ch.size.width; - float h = ch.size.height; - float ratio = w / h; +/* void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection, Color *color) { */ +/* use_shader(shader); */ +/* if (color != NULL) { */ +/* set_vec3f(shader, "textColor", color->r / 255.f, color->g / 255.f, color->b / 255.f); */ +/* } else { */ +/* set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); */ +/* } */ +/* set_mat4f(shader, "transform", (float *)projection); */ +/* glActiveTexture(GL_TEXTURE0); */ +/* glBindVertexArray(vao); */ - // draw the glyph over the entire board and then scale it down to the size - // of a single cell - float vert_h = 1.0; - float vert_w = vert_h * ratio; - // get size of a single cell in opengl coords (-1, 1) - float single_cell = (1.f/9.f) * 2; - // we minus 4 since we start at column and row 4 (0-indexed) - float offset_x = single_cell * (column - 4); - float offset_y = single_cell * (row - 4); +/* int c = 0; */ +/* while (text[c] != '\0') { */ +/* Character ch = characters[(int)text[c]]; */ - // texture coords are y-flipped - float vertices[6][4] = { - // top left tri - { ((-(vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 0.0f, 1.0f }, - { ((-(vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 0.0f, 0.0f }, - { (( (vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 1.0f, 0.0f }, +/* float xpos = pos.x + ch.bearing.width * scale; */ +/* float ypos = pos.y - (ch.size.height - ch.bearing.height) * scale; */ - // bottom right tri - { ((-(vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 0.0f, 1.0f }, - { (( (vert_w) / w * ratio) * scale) + offset_x, ( vert_h / h * scale) - offset_y, 1.0f, 0.0f }, - { (( (vert_w) / w * ratio) * scale) + offset_x, (-vert_h / h * scale) - offset_y, 1.0f, 1.0f } - }; +/* float w = (ch.size.width * scale); */ +/* float h = (ch.size.height * scale); */ - glBindTexture(GL_TEXTURE_2D, ch.texture_id); +/* float vertices[6][4] = { */ +/* // top left tri */ +/* { xpos, ypos + h, 0.0f, 0.0f }, */ +/* { xpos, ypos, 0.0f, 1.0f }, */ +/* { xpos + w, ypos, 1.0f, 1.0f }, */ - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); +/* // bottom right tri */ +/* { xpos, ypos + h, 0.0f, 0.0f }, */ +/* { xpos + w, ypos, 1.0f, 1.0f }, */ +/* { xpos + w, ypos + h, 1.0f, 0.0f } */ +/* }; */ - glBindBuffer(GL_ARRAY_BUFFER, 0); +/* glBindTexture(GL_TEXTURE_2D, ch.texture_id); */ - glDrawArrays(GL_TRIANGLES, 0, 6); +/* glBindBuffer(GL_ARRAY_BUFFER, vbo); */ +/* glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); */ +/* glBindBuffer(GL_ARRAY_BUFFER, 0); */ - glBindVertexArray(0); - glBindTexture(GL_TEXTURE_2D, 0); -} +/* glDrawArrays(GL_TRIANGLES, 0, 6); */ -void draw_text_center(Shader shader, const char *text, Size text_size, float scale, unsigned int vao, unsigned int vbo, float *transform, float offset_x, float offset_y) { - use_shader(shader); - set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); - set_mat4f(shader, "transform", transform); - glActiveTexture(GL_TEXTURE0); - glBindVertexArray(vao); +/* pos.x += (ch.advance >> 6) * scale; */ +/* c++; */ +/* } */ - int c = 0; - int x = 0; - while (text[c] != '\0') { - Character ch = characters[(int)text[c]]; - - float xpos = ((x + ch.bearing.width * scale + offset_x) / 900.f) - ((text_size.width / 2.f) * scale / 900.f); - float ypos = (((ch.bearing.height - ch.size.height + offset_y) * scale) / 900.f) - ((text_size.height / 2.f) * scale / 900.f); - - float w = (ch.size.width * scale / 900.f); - float h = (ch.size.height * scale / 900.f); - - float vertices[6][4] = { - // top left tri - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos, ypos, 0.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - - // bottom right tri - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - { xpos + w, ypos + h, 1.0f, 0.0f } - }; - - glBindTexture(GL_TEXTURE_2D, ch.texture_id); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDrawArrays(GL_TRIANGLES, 0, 6); - - x += (ch.advance >> 6) * scale; - c++; - } - - glBindVertexArray(0); - glBindTexture(GL_TEXTURE_2D, 0); -} - -Color default_text_color = { 200.f / 255.f, 200.f / 255.f, 200.f / 255.f, 1.f }; - -void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection, Color *color) { - use_shader(shader); - if (color != NULL) { - set_vec3f(shader, "textColor", color->r / 255.f, color->g / 255.f, color->b / 255.f); - } else { - set_vec3f(shader, "textColor", 200.f / 255.f, 200.f / 255.f, 200.f / 255.f); - } - set_mat4f(shader, "transform", (float *)projection); - glActiveTexture(GL_TEXTURE0); - glBindVertexArray(vao); - - int c = 0; - while (text[c] != '\0') { - Character ch = characters[(int)text[c]]; - - float xpos = pos.x + ch.bearing.width * scale; - float ypos = pos.y - (ch.size.height - ch.bearing.height) * scale; - - float w = (ch.size.width * scale); - float h = (ch.size.height * scale); - - float vertices[6][4] = { - // top left tri - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos, ypos, 0.0f, 1.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - - // bottom right tri - { xpos, ypos + h, 0.0f, 0.0f }, - { xpos + w, ypos, 1.0f, 1.0f }, - { xpos + w, ypos + h, 1.0f, 0.0f } - }; - - glBindTexture(GL_TEXTURE_2D, ch.texture_id); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glDrawArrays(GL_TRIANGLES, 0, 6); - - pos.x += (ch.advance >> 6) * scale; - c++; - } - - glBindVertexArray(0); - glBindTexture(GL_TEXTURE_2D, 0); -} +/* glBindVertexArray(0); */ +/* glBindTexture(GL_TEXTURE_2D, 0); */ +/* } */ diff --git a/font.h b/font.h index a3074e5..d7dee29 100644 --- a/font.h +++ b/font.h @@ -3,17 +3,6 @@ #include "cudoku.h" #include "shader.h" -typedef struct Size { - int width; - int height; -} Size; - -typedef struct Character { - unsigned int texture_id; - Size size; - Size bearing; - unsigned int advance; -} Character; int init_fonts(const char *font_path); void prepare_font(unsigned int *vao, unsigned int *vbo); diff --git a/helper.c b/helper.c index 7e3e4d2..1fc8274 100644 --- a/helper.c +++ b/helper.c @@ -27,3 +27,17 @@ Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, floa int max(int a, int b) { return a > b ? a : b; } + +float maxf(float a, float b) { + return a > b ? a : b; +} + +float clampf(float value, float min, float max) { + if (value < min) { + return min; + } else if (value > max) { + return max; + } + + return value; +} diff --git a/helper.h b/helper.h index c2c0f9d..0545522 100644 --- a/helper.h +++ b/helper.h @@ -4,5 +4,8 @@ typedef struct { float m[4][4]; } Matrix4x4; +Matrix4x4 identity(); Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, float top); int max(int a, int b); +float maxf(float a, float b); +float clampf(float value, float min, float max); diff --git a/main.c b/main.c index dd9d1d9..5b1c6e6 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -5,15 +6,14 @@ #include #include "3rdparty/stb/stb_image.h" -#include "x11.h" #include "cudoku.h" -#include "font.h" +#include "ui.h" #include "audio.h" #define DEBUG 1 -Display *display; -Window window; +const char *font_path = "assets/fonts/Rubik/Rubik-VariableFont_wght.ttf"; +const char *title = "Cudoku"; void usage() { printf("Usage: cudoku [OPTION]\n\n"); @@ -73,7 +73,7 @@ void handle_keypress(XEvent xev, Cudoku *game) { } else if (XLookupKeysym(&xev.xkey, 0) == XK_n) { generate_random_board(game); } else if (XLookupKeysym(&xev.xkey, 0) == XK_F11) { - toggle_fullscreen(display, window); + /* toggle_fullscreen(display, window); */ } else if (XLookupKeysym(&xev.xkey, 0) == XK_c) { toggle_check(game); /* } else if (XLookupKeysym(&xev.xkey, 0) == XK_m) { */ @@ -86,17 +86,8 @@ void handle_keypress(XEvent xev, Cudoku *game) { } int main(int argc, char *argv[]) { - int res = audio_init(); - if (res != 0) { - printf("[ERROR]: failed to initialize audio\n"); - return 1; - } - - const char *font_path = "assets/fonts/Rubik/Rubik-VariableFont_wght.ttf"; bool use_texture = false; - // 900 is the initial width and height - int width = 900, height = 900; // scale factors for the entire board to nicely fit as a square inside // the window float x_scale = 1.f, y_scale = 1.f; @@ -130,15 +121,10 @@ int main(int argc, char *argv[]) { } } - res = init_x11(&display, &window); - if (res < 0) return 1; - - res = init_fonts(font_path); - if (res == -1) { - printf("[ERROR]: could not initialize freetype library\n"); - return 1; - } else if (res == -2) { - printf("[ERROR]: could not load font file: \"%s\"\n", font_path); + Color clear_color = {0.2f, 0.15f, 0.5f, 1.f}; + int res = init_zephr(font_path, title, (Size){800, 600}, &clear_color); + if (res != 0) { + printf("[ERROR]: could not initialize zephr\n"); return 1; } @@ -148,10 +134,6 @@ int main(int argc, char *argv[]) { game.should_draw_help = true; generate_random_board(&game); - unsigned int font_vao, font_vbo; - prepare_font(&font_vao, &font_vbo); - Shader font_shader = create_shader("shaders/font_v.vert", "shaders/font_f.frag"); - Shader grid_shader; unsigned int grid_texture; if (!use_texture) @@ -170,107 +152,147 @@ int main(int argc, char *argv[]) { unsigned int win_vao = prepare_win_overlay(); float delta_t, last_t = 0.0; - start_internal_timer(); timer_start(&game.help_timer, 5.0f); timer_start(&game.timer, 0.0f); - bool quit = false; - while (!quit) { + while (!zephr_should_quit()) { float now = get_time(); delta_t = now - last_t; last_t = now; - while (XPending(display)) { - XEvent xev; - XNextEvent(display, &xev); + Size window_size = zephr_get_window_size(); - if (xev.type == ConfigureNotify) { - XConfigureEvent xce = xev.xconfigure; + /* int width = 15.f; */ + /* int height = 35.f; */ + /* int padding = 10.f; */ - if (xce.width != width || xce.height != height) { - width = xce.width; - height = xce.height; - resize_x11_window(display, window); - set_scale_factor(xce.width, xce.height, &x_scale, &y_scale); - } + float ratio_w = (float)window_size.width / 1920.f; + float ratio_h = (float)window_size.height / 1080.f; - } else if (xev.type == KeyPress) { - if (XLookupKeysym(&xev.xkey, 0) == XK_Escape || - (XLookupKeysym(&xev.xkey, 0) == XK_q && xev.xkey.state & ControlMask)) { - quit = true; - break; - } else { - handle_keypress(xev, &game); - } - } else if (xev.type == ButtonPress) { - if (xev.xbutton.button == Button1) { - int x = xev.xbutton.x; - int y = xev.xbutton.y; - do_selection(&game, x, y, width, height, x_scale, y_scale); - } - } + int width = window_size.height; + int height = window_size.width; + + if (window_size.height > window_size.width) { + width = window_size.width; + height = window_size.width; + } else { + width = window_size.height; + height = window_size.height; } - glClearColor(0.2, 0.2, 0.2, 1.0); - glClear(GL_COLOR_BUFFER_BIT); + draw_win(); - float transform[4][4] = { - {x_scale, 0, 0, 0}, - {0, y_scale, 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1}, - }; + /* draw_quad( */ + /* (Vec2){0.0, 0.0}, */ + /* (Size){width, height}, */ + /* &(Color){240.f, 235.f, 227.f, 255.f}, */ + /* ALIGN_CENTER); */ + /* UIConstraints constraints = {0}; */ + /* set_x_constraint(&constraints, 0.0, UI_CONSTRAINT_FIXED); */ + /* set_y_constraint(&constraints, 0.0, UI_CONSTRAINT_FIXED); */ + /* set_width_constraint(&constraints, 50, UI_CONSTRAINT_FIXED); */ + /* set_height_constraint(&constraints, 50, UI_CONSTRAINT_FIXED); */ - Matrix4x4 projection = orthographic_projection_2d(0.0f, width, 0.0f, height); + /* draw_quad( */ + /* constraints, */ + /* &(Color){240.f, 235.f, 227.f, 255.f}, */ + /* ALIGN_TOP_LEFT */ + /* ); */ - if (!use_texture) - draw_bg_grid_shader(grid_shader, board_vao, (float *)transform, width < height ? width : height); - else - draw_bg_grid_texture(grid_shader, board_vao, grid_texture, (float *)transform); + /* Size size = calculate_text_size("Bruh", 1.0f); */ + /* float x = clampf(500.0f * timer_elapsed(&my_time), 0.0, window_size.width - size.width); */ + /* draw_text("Bruh", 1.0f, (Vec2){0.0, size.height}, &(Color){255.f, 0.f, 0.f, 255.f}); */ - if (game.should_draw_selection) - draw_selection_box( - selection_shader, - selection_vao, - selection_vbo, - game.selection.x, - game.selection.y, - (float *)transform, - selection_color); - - draw_numbers(font_shader, font_vao, font_vbo, (float *)transform, game.board); - - if (game.should_highlight_mistakes) { - highlight_mistakes(selection_shader, selection_vao, selection_vbo, (float *)transform, &game); - - char* text = "Mistake highlighter is ON"; - float text_scale = 0.2; - Size text_size = calculate_text_size(text, text_scale); - Vec2 text_pos = { 0.0f, text_size.height - 10.f }; - Color text_color = { 200.0f, 50.0f, 50.0f, 1.0f }; - draw_text_at(font_shader, text, text_pos, text_scale, font_vao, font_vbo, (float *)projection.m, &text_color); - } - - draw_timer(font_shader, font_vao, font_vbo, (float *)projection.m, &game.timer, width); - - if (timer_ended(&game.help_timer)) { - timer_stop(&game.help_timer); - game.should_draw_help = false; - } - - if (game.should_draw_help) - draw_help_overlay(selection_shader, font_shader, selection_vao, selection_vbo, font_vao, font_vbo, &projection, height, &game.help_timer); - - if (game.has_won) { - draw_win_overlay(win_shader, font_shader, win_vao, font_vao, font_vbo, (float *)transform, &game); - } - - glXSwapBuffers(display, window); - audio_update(); + zephr_swap_buffers(); } + /* while (!quit) { */ - audio_cleanup(); - close_x11(&display, &window); + /* /1* while (XPending(display)) { *1/ */ + /* /1* XEvent xev; *1/ */ + /* /1* XNextEvent(display, &xev); *1/ */ + + /* /1* if (xev.type == ConfigureNotify) { *1/ */ + /* /1* XConfigureEvent xce = xev.xconfigure; *1/ */ + + /* /1* if (xce.width != width || xce.height != height) { *1/ */ + /* /1* width = xce.width; *1/ */ + /* /1* height = xce.height; *1/ */ + /* /1* resize_x11_window(display, window); *1/ */ + /* /1* set_scale_factor(xce.width, xce.height, &x_scale, &y_scale); *1/ */ + /* /1* } *1/ */ + + /* /1* } else if (xev.type == KeyPress) { *1/ */ + /* /1* if (XLookupKeysym(&xev.xkey, 0) == XK_Escape || *1/ */ + /* /1* (XLookupKeysym(&xev.xkey, 0) == XK_q && xev.xkey.state & ControlMask)) { *1/ */ + /* /1* quit = true; *1/ */ + /* /1* break; *1/ */ + /* /1* } else { *1/ */ + /* /1* handle_keypress(xev, &game); *1/ */ + /* /1* } *1/ */ + /* /1* } else if (xev.type == ButtonPress) { *1/ */ + /* /1* if (xev.xbutton.button == Button1) { *1/ */ + /* /1* int x = xev.xbutton.x; *1/ */ + /* /1* int y = xev.xbutton.y; *1/ */ + /* /1* do_selection(&game, x, y, width, height, x_scale, y_scale); *1/ */ + /* /1* } *1/ */ + /* /1* } *1/ */ + /* /1* } *1/ */ + + /* float transform[4][4] = { */ + /* {x_scale, 0, 0, 0}, */ + /* {0, y_scale, 0, 0}, */ + /* {0, 0, 1, 0}, */ + /* {0, 0, 0, 1}, */ + /* }; */ + + /* /1* Matrix4x4 projection = orthographic_projection_2d(0.0f, width, 0.0f, height); *1/ */ + + /* /1* if (!use_texture) *1/ */ + /* /1* draw_bg_grid_shader(grid_shader, board_vao, (float *)transform, width < height ? width : height); *1/ */ + /* /1* else *1/ */ + /* /1* draw_bg_grid_texture(grid_shader, board_vao, grid_texture, (float *)transform); *1/ */ + + /* if (game.should_draw_selection) */ + /* draw_selection_box( */ + /* selection_shader, */ + /* selection_vao, */ + /* selection_vbo, */ + /* game.selection.x, */ + /* game.selection.y, */ + /* (float *)transform, */ + /* selection_color); */ + + /* /1* draw_numbers(font_shader, font_vao, font_vbo, (float *)transform, game.board); *1/ */ + + /* /1* if (game.should_highlight_mistakes) { *1/ */ + /* /1* highlight_mistakes(selection_shader, selection_vao, selection_vbo, (float *)transform, &game); *1/ */ + + /* /1* char* text = "Mistake highlighter is ON"; *1/ */ + /* /1* float text_scale = 0.2; *1/ */ + /* /1* Size text_size = calculate_text_size(text, text_scale); *1/ */ + /* /1* Vec2 text_pos = { 0.0f, text_size.height - 10.f }; *1/ */ + /* /1* Color text_color = { 200.0f, 50.0f, 50.0f, 1.0f }; *1/ */ + /* /1* draw_text_at(font_shader, text, text_pos, text_scale, font_vao, font_vbo, (float *)projection.m, &text_color); *1/ */ + /* /1* } *1/ */ + + /* /1* draw_timer(font_shader, font_vao, font_vbo, (float *)projection.m, &game.timer, width); *1/ */ + + /* if (timer_ended(&game.help_timer)) { */ + /* timer_stop(&game.help_timer); */ + /* game.should_draw_help = false; */ + /* } */ + + /* /1* if (game.should_draw_help) *1/ */ + /* /1* draw_help_overlay(selection_shader, font_shader, selection_vao, selection_vbo, font_vao, font_vbo, &projection, height, &game.help_timer); *1/ */ + + /* /1* if (game.has_won) { *1/ */ + /* /1* draw_win_overlay(win_shader, font_shader, win_vao, font_vao, font_vbo, (float *)transform, &game); *1/ */ + /* /1* } *1/ */ + + /* /1* glXSwapBuffers(display, window); *1/ */ + /* } */ + + deinit_zephr(); return 0; } diff --git a/shaders/font_f.frag b/shaders/font.frag similarity index 70% rename from shaders/font_f.frag rename to shaders/font.frag index 0db265d..b56b9a1 100644 --- a/shaders/font_f.frag +++ b/shaders/font.frag @@ -3,9 +3,9 @@ in vec2 TexCoords; out vec4 FragColor; uniform sampler2D text; -uniform vec3 textColor; +uniform vec4 textColor; void main() { vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r); - FragColor = vec4(textColor, 1.0) * sampled; + FragColor = textColor * sampled; } diff --git a/shaders/font_v.vert b/shaders/font.vert similarity index 52% rename from shaders/font_v.vert rename to shaders/font.vert index b08c877..662998c 100644 --- a/shaders/font_v.vert +++ b/shaders/font.vert @@ -2,9 +2,10 @@ layout (location = 0) in vec4 vertex; out vec2 TexCoords; -uniform mat4 transform; +uniform mat4 projection; +uniform mat4 model; void main() { - gl_Position = transform * vec4(vertex.xy, 0.0, 1.0); + gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0); TexCoords = vertex.zw; } diff --git a/shaders/ui.frag b/shaders/ui.frag new file mode 100644 index 0000000..19f3afe --- /dev/null +++ b/shaders/ui.frag @@ -0,0 +1,9 @@ +#version 330 core + +in vec2 texCoords; +out vec4 FragColor; +uniform vec4 aColor; + +void main() { + FragColor = vec4(aColor); +} diff --git a/shaders/ui.vert b/shaders/ui.vert new file mode 100644 index 0000000..3834ce0 --- /dev/null +++ b/shaders/ui.vert @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoords; +out vec2 texCoords; + +uniform mat4 projection; + +void main() { + gl_Position = projection * vec4(aPos, 1.0f); + texCoords = aTexCoords; +} diff --git a/timer.c b/timer.c index 2d5f34f..2e98246 100644 --- a/timer.c +++ b/timer.c @@ -25,6 +25,10 @@ void timer_start(Timer *timer, float duration) { timer->is_running = true; } +void timer_stop(Timer *timer) { + timer->is_running = false; +} + void timer_reset(Timer *timer) { timer->time = get_time(); timer->is_running = true; @@ -34,10 +38,6 @@ float timer_remaining(Timer *timer) { return timer->duration - (get_time() - timer->time); } -void timer_stop(Timer *timer) { - timer->is_running = false; -} - float timer_elapsed(Timer *timer) { return get_time() - timer->time; } diff --git a/timer.h b/timer.h index f858168..db1fd00 100644 --- a/timer.h +++ b/timer.h @@ -16,7 +16,7 @@ double get_time(); void start_internal_timer(); bool timer_ended(Timer *timer); void timer_start(Timer *timer, float duration); +void timer_stop(Timer *timer); void timer_reset(Timer *timer); float timer_remaining(Timer *timer); -void timer_stop(Timer *timer); float timer_elapsed(Timer *timer); diff --git a/ui.c b/ui.c new file mode 100644 index 0000000..37c0190 --- /dev/null +++ b/ui.c @@ -0,0 +1,499 @@ +#include +#include + +#include +#include FT_FREETYPE_H +#include +#include +#include + +#include "cudoku.h" +#include "shader.h" +#include "audio.h" +#include "ui.h" +#include "x11.h" + +Display *display; +Window window; + +Context zephr_context; +Character characters[128]; +Shader font_shader; +Shader ui_shader; +unsigned int font_vao; +unsigned int ui_vao; +unsigned int font_vbo; +unsigned int ui_vbo; + +// TODO: instanced rendering of ui elements and text?? + +int init_fonts(const char *font_path) { + FT_Library ft; + if (FT_Init_FreeType(&ft)) { + return -1; + } + + FT_Face face; + if (FT_New_Face(ft, font_path, 0, &face)) { + return -2; + } + + if ((face->face_flags & FT_FACE_FLAG_MULTIPLE_MASTERS)) { + printf("[INFO] Got a variable font\n"); + FT_MM_Var *mm; + FT_Get_MM_Var(face, &mm); + + FT_Set_Var_Design_Coordinates(face, mm->num_namedstyles, mm->namedstyle[mm->num_namedstyles - 4].coords); + + FT_Done_MM_Var(ft, mm); + } + + FT_Set_Pixel_Sizes(face, 0, 128); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + for (int c = 0; c < 128; c++) { + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) { + printf("[ERROR]: failed to load glyph for char '%c'\n", c); + } + + unsigned int texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width, + face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, + face->glyph->bitmap.buffer); + + 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_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + characters[c].texture_id = texture; + characters[c].advance = face->glyph->advance.x; + characters[c].size = (Size){ .width = face->glyph->bitmap.width, .height = face->glyph->bitmap.rows }; + characters[c].bearing = (Size){ .width = face->glyph->bitmap_left, .height = face->glyph->bitmap_top }; + } + + glBindTexture(GL_TEXTURE_2D, 0); + + FT_Done_Face(face); + FT_Done_FreeType(ft); + + return 0; +} + +int init_ui(const char* font_path, Size window_size) { + zephr_context.window_size = window_size; + zephr_context.projection = orthographic_projection_2d(0.f, window_size.width, window_size.height, 0.f); + + int res = init_fonts(font_path); + if (res == -1) { + printf("[ERROR]: could not initialize freetype library\n"); + return 1; + } else if (res == -2) { + printf("[ERROR]: could not load font file: \"%s\"\n", font_path); + return 1; + } + + font_shader = create_shader("shaders/font.vert", "shaders/font.frag"); + ui_shader = create_shader("shaders/ui.vert", "shaders/ui.frag"); + + + glGenVertexArrays(1, &font_vao); + glGenVertexArrays(1, &ui_vao); + glGenBuffers(1, &font_vbo); + glGenBuffers(1, &ui_vbo); + + glBindVertexArray(font_vao); + glBindBuffer(GL_ARRAY_BUFFER, font_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); + + glBindVertexArray(ui_vao); + glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + return 0; +} + +int init_zephr(const char* font_path, const char* window_title, Size window_size, Color *clear_color) { + int res = audio_init(); + if (res != 0) { + printf("[ERROR]: failed to initialize audio\n"); + return 1; + } + + res = init_x11(&display, &window, window_title, window_size.width, window_size.height); + if (res < 0) return 1; + + res = init_ui(font_path, (Size){ .width = window_size.width, .height = window_size.height }); + if (res != 0) { + printf("[ERROR]: failed to initialize ui\n"); + return 1; + } + + if (clear_color) { + zephr_context.clear_color = *clear_color; + } else { + zephr_context.clear_color = (Color){0.0, 0.0, 0.0, 1.0}; + } + + start_internal_timer(); + + return 0; +} + +void deinit_zephr() { + close_x11(&display, &window); + audio_close(); +} + +bool zephr_should_quit() { + // TODO: input events + // TODO: window events + // TODO: ui update?? + + while (XPending(display)) { + XEvent xev; + XNextEvent(display, &xev); + + if (xev.type == ConfigureNotify) { + XConfigureEvent xce = xev.xconfigure; + + if (xce.width != zephr_context.window_size.width || xce.height != zephr_context.window_size.height) { + zephr_context.window_size = (Size){ .width = xce.width, .height = xce.height }; + zephr_context.projection = orthographic_projection_2d(0.f, xce.width, xce.height, 0.f); + resize_x11_window(display, window); + /* set_scale_factor(xce.width, xce.height, &x_scale, &y_scale); */ + } + + } else if (xev.type == KeyPress) { + if (XLookupKeysym(&xev.xkey, 0) == XK_Escape || + (XLookupKeysym(&xev.xkey, 0) == XK_q && xev.xkey.state & ControlMask)) { + zephr_context.should_quit = true; + break; + } else { + /* handle_keypress(xev, &game); */ + } + } else if (xev.type == ButtonPress) { + /* if (xev.xbutton.button == Button1) { */ + /* int x = xev.xbutton.x; */ + /* int y = xev.xbutton.y; */ + /* do_selection(&game, x, y, width, height, x_scale, y_scale); */ + /* } */ + } + } + + float r = zephr_context.clear_color.r; + float g = zephr_context.clear_color.g; + float b = zephr_context.clear_color.b; + float a = zephr_context.clear_color.a; + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); + + /* update_ui(); */ + audio_update(); + + return zephr_context.should_quit; +} + +void zephr_swap_buffers() { + glXSwapBuffers(display, window); +} + +Size zephr_get_window_size() { + return zephr_context.window_size; +} + +Sizef calculate_text_size(const char *text, float scale) { + Sizef size = { .width = 0, .height = 0 }; + int w = 0; + int h = 0; + int max_bearing_h = 0; + + // NOTE: I don't like looping through the characters twice, but it's fine for now + for (uint i = 0; i < strlen(text); i++) { + Character ch = characters[(uint)text[i]]; + max_bearing_h = max(max_bearing_h, ch.bearing.height); + } + + for (uint i = 0; i < strlen(text); i++) { + Character ch = characters[(uint)text[i]]; + w += ch.advance / 64; + + // remove bearing of first character + if (i == 0 && strlen(text) > 1) { + w -= ch.bearing.width; + } + + // remove the trailing width of the last character + if (i == strlen(text) - 1) { + w -= ((ch.advance / 64) - (ch.bearing.width + ch.size.width)); + } + + // if we only have one character in the text, then remove the bearing width + if (strlen(text) == 1) { + w -= (ch.bearing.width); + } + + h = max(h, max_bearing_h - ch.bearing.height + ch.size.height); + } + size.width = (float)w * scale; + size.height = (float)h * scale; + return size; +} + +void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type) { + switch (type) { + case UI_CONSTRAINT_RELATIVE_PIXELS: + case UI_CONSTRAINT_ASPECT_RATIO: + // no-op. fallback to FIXED + case UI_CONSTRAINT_FIXED: + constraints->x = value; + break; + case UI_CONSTRAINT_RELATIVE: + constraints->x = (value * zephr_context.window_size.width); + break; + } +} + +void set_y_constraint(UIConstraints *constraints, float value, UIConstraint type) { + switch (type) { + case UI_CONSTRAINT_RELATIVE_PIXELS: + case UI_CONSTRAINT_ASPECT_RATIO: + // no-op. fallback to FIXED + case UI_CONSTRAINT_FIXED: + constraints->y = value; + break; + case UI_CONSTRAINT_RELATIVE: + constraints->y = (value * zephr_context.window_size.height); + break; + } +} + +void set_width_constraint(UIConstraints *constraints, float value, UIConstraint type) { + switch (type) { + case UI_CONSTRAINT_FIXED: + constraints->width = value; + break; + case UI_CONSTRAINT_RELATIVE: + constraints->width = (value * zephr_context.window_size.width); + break; + case UI_CONSTRAINT_RELATIVE_PIXELS: + constraints->width = zephr_context.window_size.width / 1920.f * value; + break; + case UI_CONSTRAINT_ASPECT_RATIO: + constraints->width = constraints->height * value; + break; + } +} + +void set_height_constraint(UIConstraints *constraints, float value, UIConstraint type) { + switch (type) { + case UI_CONSTRAINT_FIXED: + constraints->height = value; + break; + case UI_CONSTRAINT_RELATIVE: + constraints->height = (value * zephr_context.window_size.height); + break; + case UI_CONSTRAINT_RELATIVE_PIXELS: + constraints->height = zephr_context.window_size.height / 1080.f * value; + break; + case UI_CONSTRAINT_ASPECT_RATIO: + constraints->height = constraints->width * value; + break; + } +} + +void apply_constraints(UIConstraints constraints, Vec2f *pos, Sizef *size) { + *pos = (Vec2f){constraints.x, constraints.y}; + *size = (Sizef){constraints.width, constraints.height}; +} + +void apply_alignment(Alignment align, Vec2f *pos, Sizef size) { + switch (align) { + case ALIGN_TOP_LEFT: + pos->x = pos->x; + pos->y = pos->y; + break; + case ALIGN_TOP_CENTER: + pos->x += zephr_context.window_size.width / 2.f - size.width / 2.f; + pos->y = pos->y; + break; + case ALIGN_TOP_RIGHT: + pos->x += zephr_context.window_size.width - size.width; + pos->y = pos->y; + break; + case ALIGN_BOTTOM_LEFT: + pos->x = pos->x; + pos->y += zephr_context.window_size.height - size.height; + break; + case ALIGN_BOTTOM_CENTER: + pos->x += zephr_context.window_size.width / 2.f - size.width / 2.f; + pos->y += zephr_context.window_size.height - size.height; + break; + case ALIGN_BOTTOM_RIGHT: + pos->x += zephr_context.window_size.width - size.width; + pos->y += zephr_context.window_size.height - size.height; + break; + case ALIGN_LEFT_CENTER: + pos->x = pos->x; + pos->y += zephr_context.window_size.height / 2.f - size.height / 2.f; + break; + case ALIGN_RIGHT_CENTER: + pos->x += zephr_context.window_size.width - size.width; + pos->y += zephr_context.window_size.height / 2.f - size.height / 2.f; + break; + case ALIGN_CENTER: + pos->x += zephr_context.window_size.width / 2.f - size.width / 2.f; + pos->y += zephr_context.window_size.height / 2.f - size.height / 2.f; + break; + } +} + +void draw_quad(UIConstraints constraints, Color *color, Alignment align) { + use_shader(ui_shader); + + if (color) { + set_vec4f(ui_shader, "aColor", color->r / 255.f, color->g / 255.f, color->b / 255.f, color->a / 255.f); + } else { + set_vec4f(ui_shader, "aColor", 0.f, 0.f, 0.f, 1.f); + } + + set_mat4f(ui_shader, "projection", (float *)zephr_context.projection.m); + + glBindVertexArray(ui_vao); + + Vec2f pos = { 0.f, 0.f }; + Sizef size = { 0.f, 0.f }; + + apply_constraints(constraints, &pos, &size); + apply_alignment(align, &pos, size); + + float vertices[6][4] = { + {pos.x, pos.y + size.height, 0.0, 1.0}, + {pos.x, pos.y, 0.0, 0.0}, + {pos.x + size.width, pos.y, 1.0, 0.0}, + + {pos.x, pos.y + size.height, 0.0, 1.0}, + {pos.x + size.width, pos.y, 1.0, 0.0}, + {pos.x + size.width, pos.y + size.height, 1.0, 1.0}, + }; + + glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + glBindVertexArray(0); +} + +// TODO: remove scaling and replace it with standardized font sizes with 16.0f being the default +void draw_text(const char* text, float scale, Vec2f pos, Color *color, Alignment alignment) { + use_shader(font_shader); + if (color) { + set_vec4f(font_shader, "textColor", color->r / 255.f, color->g / 255.f, color->b / 255.f, color->a / 255.f); + } else { + set_vec4f(font_shader, "textColor", 0.f, 0.f, 0.f, 1.f); + } + set_mat4f(font_shader, "projection", (float *)zephr_context.projection.m); + Matrix4x4 model = identity(); + model.m[3][0] = pos.x; + model.m[3][1] = pos.y; + + set_mat4f(font_shader, "model", (float *)model.m); + + glActiveTexture(GL_TEXTURE0); + glBindVertexArray(font_vao); + + Sizef text_size = calculate_text_size(text, 1.0); + + int max_bearing_h = 0; + for (uint i = 0; i < strlen(text); i++) { + Character ch = characters[(int)text[i]]; + max_bearing_h = max(max_bearing_h, ch.bearing.height); + } + + float first_char_bearing_w = characters[(int)text[0]].bearing.width; + + int c = 0; + int x = 0; + while (text[c] != '\0') { + Character ch = characters[(int)text[c]]; + + // subtract the bearing width of the first character to remove the extra space + // at the start of the text and move every char to the left by that width + float xpos = (x + (ch.bearing.width - first_char_bearing_w)) * scale; + + float ypos = (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h)) * scale; + Vec2f final_pos = { xpos, ypos }; + + float w = (ch.size.width * scale); + float h = (ch.size.height * scale); + + apply_alignment(alignment, &final_pos, (Sizef){ text_size.width * scale, text_size.height * scale }); + + float vertices[6][4] = { + // top left tri + {final_pos.x, final_pos.y + h, 0.0, 1.0}, + {final_pos.x, final_pos.y, 0.0, 0.0}, + {final_pos.x + w, final_pos.y, 1.0, 0.0}, + + // bottom right tri + {final_pos.x, final_pos.y + h, 0.0, 1.0}, + {final_pos.x + w, final_pos.y, 1.0, 0.0}, + {final_pos.x + w, final_pos.y + h, 1.0, 1.0}, + }; + + glBindTexture(GL_TEXTURE_2D, ch.texture_id); + + glBindBuffer(GL_ARRAY_BUFFER, font_vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + x += (ch.advance >> 6); + c++; + } + + glBindVertexArray(0); + glBindTexture(GL_TEXTURE_2D, 0); +} + +/* enum Element { */ +/* Rect, */ +/* Circle, */ +/* }; */ + +/* struct UIElement { */ +/* Vec2 pos; */ +/* Size size; */ +/* Color *color; */ +/* enum Element type; */ +/* }; */ + +/* struct UIText { */ +/* Vec2 pos; */ +/* float scale; */ +/* Color *color; */ +/* const char* text; */ +/* }; */ + +/* struct UIElement *create_element(Vec2 pos, Size size, Color *color, enum Element type) { */ +/* struct UIElement *element = malloc(sizeof(struct UIElement)); */ +/* element->pos = pos; */ +/* element->size = size; */ +/* element->color = color; */ +/* element->type = type; */ +/* return element; */ +/* } */ + diff --git a/ui.h b/ui.h new file mode 100644 index 0000000..0a2e8f0 --- /dev/null +++ b/ui.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +#include "helper.h" + +typedef struct Size { + int width; + int height; +} Size; + +typedef struct Sizef { + float width; + float height; +} Sizef; + +typedef struct Vec2 { + int x; + int y; +} Vec2; + +typedef struct Vec2f { + float x; + float y; +} Vec2f; + +typedef struct Color { + float r; + float g; + float b; + float a; +} Color; + +typedef struct Character { + unsigned int texture_id; + Size size; + Size bearing; + unsigned int advance; +} Character; + +typedef struct { + bool should_quit; + Color clear_color; + Size window_size; + + Matrix4x4 projection; +} Context; + +typedef enum Alignment { + ALIGN_TOP_LEFT, + ALIGN_TOP_CENTER, + ALIGN_TOP_RIGHT, + ALIGN_LEFT_CENTER, + ALIGN_CENTER, + ALIGN_RIGHT_CENTER, + ALIGN_BOTTOM_LEFT, + ALIGN_BOTTOM_CENTER, + ALIGN_BOTTOM_RIGHT, +} Alignment; + +typedef enum UIConstraint { + UI_CONSTRAINT_FIXED, + UI_CONSTRAINT_RELATIVE, + UI_CONSTRAINT_RELATIVE_PIXELS, + UI_CONSTRAINT_ASPECT_RATIO, +} UIConstraint; + +typedef struct UIConstraints { + float x; + float y; + float width; + float height; +} UIConstraints; + +int init_zephr(const char* font_path, const char* window_title, Size window_size, Color *clear_color); +void deinit_zephr(); +bool zephr_should_quit(); +void zephr_swap_buffers(); +Size zephr_get_window_size(); +void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type); +void set_y_constraint(UIConstraints *constraints, float value, UIConstraint type); +void set_width_constraint(UIConstraints *constraints, float value, UIConstraint type); +void set_height_constraint(UIConstraints *constraints, float value, UIConstraint type); +void draw_quad(UIConstraints constraints, Color *color, Alignment align); +void draw_text(const char* text, float scale, Vec2f pos, Color *color, Alignment align); +Sizef calculate_text_size(const char *text, float scale); diff --git a/x11.c b/x11.c index 04360b8..6e9c1aa 100644 --- a/x11.c +++ b/x11.c @@ -4,12 +4,10 @@ #include -static const uint win_width = 900; -static const uint win_height = 900; static bool fullscreen = false; Colormap colormap; -GLXContext context; +GLXContext glx_context; void resize_x11_window(Display *display, Window window) { XWindowAttributes win_attrs; @@ -17,7 +15,7 @@ void resize_x11_window(Display *display, Window window) { glViewport(0, 0, win_attrs.width, win_attrs.height); } -int init_x11(Display **display, Window *window) { +int init_x11(Display **display, Window *window, const char* title, int window_width, int window_height) { *display = XOpenDisplay(NULL); if (display == NULL) { @@ -36,12 +34,12 @@ int init_x11(Display **display, Window *window) { StructureNotifyMask | ButtonPressMask; attributes.colormap = colormap; - *window = XCreateWindow(*display, root, 0, 0, win_width, win_height, 0, + *window = XCreateWindow(*display, root, 0, 0, window_width, window_height, 0, DefaultDepth(*display, screen), InputOutput, visual, CWColormap | CWEventMask, &attributes); XMapWindow(*display, *window); - XStoreName(*display, *window, "Cudoku"); + XStoreName(*display, *window, title); if (!window) { printf("Failed to create window\n"); @@ -74,15 +72,15 @@ int init_x11(Display **display, Window *window) { None }; - context = glXCreateContextAttribsARB(*display, fbc[0], NULL, 1, + glx_context = glXCreateContextAttribsARB(*display, fbc[0], NULL, 1, context_attributes); - if (!context) { + if (!glx_context) { printf("Failed to create GLX context\n"); return 1; } - glXMakeCurrent(*display, *window, context); + glXMakeCurrent(*display, *window, glx_context); int gl_version = gladLoaderLoadGL(); @@ -108,7 +106,7 @@ int init_x11(Display **display, Window *window) { void close_x11(Display **display, Window *window) { glXMakeCurrent(*display, 0, 0); - glXDestroyContext(*display, context); + glXDestroyContext(*display, glx_context); XDestroyWindow(*display, *window); XFreeColormap(*display, colormap); diff --git a/x11.h b/x11.h index ae7aaa4..caa9c6d 100644 --- a/x11.h +++ b/x11.h @@ -2,7 +2,7 @@ #include -void resize_x11_window(Display *display, Window window); -int init_x11(Display **display, Window *window); +int init_x11(Display **display, Window *window, const char* title, int window_width, int window_height); void close_x11(Display **display, Window *window); +void resize_x11_window(Display *display, Window window); void toggle_fullscreen(Display *display, Window window);