mirror of
https://github.com/IllusionMan1212/cudoku.git
synced 2026-10-24 02:12:25 +00:00
restructure zephr
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
BIN=cudoku
|
||||
CC=gcc
|
||||
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
|
||||
OBJ=main.o x11.o cudoku.o shader.o text.o audio.o timer.o ui.o zephr.o zephr_math.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/stb/stb_ds.h 3rdparty/fmod/include/fmod.h
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
#include "3rdparty/stb/stb_image.h"
|
||||
|
||||
#include "cudoku.h"
|
||||
#include "shader.h"
|
||||
#include "audio.h"
|
||||
#include "ui.h"
|
||||
#include "text.h"
|
||||
#include "zephr.h"
|
||||
|
||||
#define HELP_TEXT_SIZE 13
|
||||
|
||||
@@ -199,18 +200,6 @@ void draw_bg_grid_texture(Shader shader, unsigned int vao, unsigned int texture,
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void draw_numbers(Shader shader, unsigned int vao, unsigned int vbo, float *transform, Cell board[9][9]) {
|
||||
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); */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
/* void draw_pause_overlay(Cudoku *game) { */
|
||||
/* timer_pause(&game->timer); */
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "helper.h"
|
||||
#include "shader.h"
|
||||
#include "timer.h"
|
||||
#include "ui.h"
|
||||
@@ -25,12 +24,7 @@ typedef struct Cudoku {
|
||||
float win_time;
|
||||
} Cudoku;
|
||||
|
||||
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);
|
||||
void draw_bg_grid_texture(Shader shader, unsigned int vao, unsigned int texture, float* transform);
|
||||
void draw_numbers(Shader shader, unsigned int vao, unsigned int vbo, float *transform, Cell board[9][9]);
|
||||
void draw_win_overlay(Shader win_shader, Shader font_shader, unsigned int vao, unsigned int font_vao, unsigned int font_vbo, float *transform, Cudoku *game);
|
||||
void prepare_selection_box(unsigned int *vao, unsigned int *vbo);
|
||||
void draw_selection_box(Shader shader, unsigned int vao, unsigned int vbo, int x, int y, float *transform, Color color);
|
||||
void highlight_mistakes(Shader shader, unsigned int vao, unsigned vbo, float *transform, Cudoku *game);
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
/* #include <glad/gl.h> */
|
||||
|
||||
/* #include "font.h" */
|
||||
/* #include "shader.h" */
|
||||
|
||||
/* 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); */
|
||||
|
||||
/* // get ascii value of cell value and use it to index into characters */
|
||||
/* Character ch = characters['0' + cell.value]; */
|
||||
|
||||
/* float w = ch.size.width; */
|
||||
/* float h = ch.size.height; */
|
||||
/* float ratio = w / h; */
|
||||
|
||||
/* // 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); */
|
||||
|
||||
/* // 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 }, */
|
||||
|
||||
/* // 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 } */
|
||||
/* }; */
|
||||
|
||||
/* 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); */
|
||||
|
||||
/* glBindVertexArray(0); */
|
||||
/* glBindTexture(GL_TEXTURE_2D, 0); */
|
||||
/* } */
|
||||
|
||||
/* 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); */
|
||||
|
||||
/* 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); */
|
||||
/* } */
|
||||
@@ -1,12 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "cudoku.h"
|
||||
#include "shader.h"
|
||||
|
||||
|
||||
int init_fonts(const char *font_path);
|
||||
void prepare_font(unsigned int *vao, unsigned int *vbo);
|
||||
Size calculate_text_size(const char *text, float scale);
|
||||
void draw_number(Shader shader, Cell cell, int row, int column, float scale,unsigned int vao, unsigned int vbo, float* transform);
|
||||
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);
|
||||
void draw_text_at(Shader shader, const char *text, Vec2 pos, float scale, unsigned int vao, unsigned int vbo, float *projection, Color *color);
|
||||
@@ -7,8 +7,8 @@
|
||||
#include <stb_image.h>
|
||||
|
||||
#include "cudoku.h"
|
||||
#include "ui.h"
|
||||
#include "audio.h"
|
||||
#include "zephr.h"
|
||||
|
||||
#define DEBUG 1
|
||||
|
||||
@@ -124,7 +124,7 @@ int main(int argc, char *argv[]) {
|
||||
printf("[ERROR]: could not initialize zephr\n");
|
||||
return 1;
|
||||
}
|
||||
make_window_non_resizable();
|
||||
zephr_make_window_non_resizable();
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
@@ -184,7 +184,6 @@ int main(int argc, char *argv[]) {
|
||||
/* /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/ */
|
||||
@@ -217,8 +216,6 @@ int main(int argc, char *argv[]) {
|
||||
/* (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/ */
|
||||
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <freetype/ftmm.h>
|
||||
#include <glad/glx.h>
|
||||
|
||||
#include "zephr.h"
|
||||
#include "shader.h"
|
||||
#include "text.h"
|
||||
|
||||
#define FONT_PIXEL_SIZE 64
|
||||
|
||||
extern Context zephr_context;
|
||||
Shader font_shader;
|
||||
unsigned int font_vao;
|
||||
unsigned int font_instance_vbo;
|
||||
|
||||
void new_glyph_instance_list(GlyphInstanceList *list, uint capacity) {
|
||||
list->size = 0;
|
||||
list->capacity = capacity;
|
||||
list->data = malloc(list->capacity * sizeof(TextInstance));
|
||||
}
|
||||
|
||||
void clear_glyph_instance_list(GlyphInstanceList *list) {
|
||||
list->size = 0;
|
||||
memset(list->data, 0, list->size * sizeof(TextInstance));
|
||||
}
|
||||
|
||||
void add_glyph_instance(GlyphInstanceList *list, TextInstance instance) {
|
||||
if (list->size >= list->capacity) {
|
||||
list->capacity *= 2;
|
||||
TextInstance* temp = realloc(list->data, list->capacity * sizeof(TextInstance));
|
||||
if (!temp) {
|
||||
printf("[FATAL] Failed to reallocate memory for glyph instance list\n");
|
||||
exit(1);
|
||||
}
|
||||
list->data = temp;
|
||||
}
|
||||
|
||||
list->data[list->size++] = instance;
|
||||
}
|
||||
|
||||
int init_freetype(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;
|
||||
}
|
||||
|
||||
// sets the variable font to be bold
|
||||
/* 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, FONT_PIXEL_SIZE);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
int pen_x = 0, pen_y = 0;
|
||||
|
||||
/* FT_UInt glyph_idx; */
|
||||
/* FT_ULong c = FT_Get_First_Char(face, &glyph_idx); */
|
||||
|
||||
uint tex_width = 0, tex_height = 0;
|
||||
for (uint i = 32; i < 128; i++) {
|
||||
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
|
||||
printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
|
||||
}
|
||||
|
||||
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
|
||||
|
||||
tex_width += face->glyph->bitmap.width + 1;
|
||||
tex_height = max(tex_height, face->glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
char *pixels = (char *)calloc(tex_width * tex_height, 1);
|
||||
|
||||
for (uint i = 32; i < 128; i++) {
|
||||
/* while (glyph_idx) { */
|
||||
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
|
||||
printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
|
||||
}
|
||||
|
||||
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
|
||||
|
||||
FT_Bitmap *bmp = &face->glyph->bitmap;
|
||||
|
||||
if (pen_x + bmp->width >= tex_width) {
|
||||
pen_x = 0;
|
||||
pen_y += (1 + (face->size->metrics.height >> 6));
|
||||
}
|
||||
|
||||
for (uint row = 0; row < bmp->rows; row++) {
|
||||
for (uint col = 0; col < bmp->width; col++) {
|
||||
int x = pen_x + col;
|
||||
int y = pen_y + row;
|
||||
pixels[y * tex_width + x] = bmp->buffer[row * bmp->pitch + col];
|
||||
}
|
||||
}
|
||||
|
||||
float atlas_x0 = (float)pen_x / (float)tex_width;
|
||||
float atlas_y0 = (float)pen_y / (float)tex_height;
|
||||
float atlas_x1 = (float)(pen_x + bmp->width) / (float)tex_width;
|
||||
float atlas_y1 = (float)(pen_y + bmp->rows) / (float)tex_height;
|
||||
|
||||
Vec2f top_left = (Vec2f){ .x = atlas_x0, .y = atlas_y1 };
|
||||
Vec2f top_right = (Vec2f){ .x = atlas_x1, .y = atlas_y1 };
|
||||
Vec2f bottom_right = (Vec2f){ .x = atlas_x1, .y = atlas_y0 };
|
||||
Vec2f bottom_left = (Vec2f){ .x = atlas_x0, .y = atlas_y0 };
|
||||
|
||||
Character character;
|
||||
|
||||
character.tex_coords[0] = top_left;
|
||||
character.tex_coords[1] = top_right;
|
||||
character.tex_coords[2] = bottom_right;
|
||||
character.tex_coords[3] = bottom_left;
|
||||
character.advance = face->glyph->advance.x;
|
||||
character.size = (Size){ .width = face->glyph->bitmap.width, .height = face->glyph->bitmap.rows };
|
||||
character.bearing = (Size){ .width = face->glyph->bitmap_left, .height = face->glyph->bitmap_top };
|
||||
|
||||
zephr_context.font.characters[i] = character;
|
||||
|
||||
pen_x += bmp->width + 1;
|
||||
|
||||
/* c = FT_Get_Next_Char(face, c, &glyph_idx); */
|
||||
}
|
||||
|
||||
unsigned int texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, tex_width,
|
||||
tex_height, 0, GL_RED, GL_UNSIGNED_BYTE,
|
||||
pixels);
|
||||
|
||||
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);
|
||||
|
||||
zephr_context.font.atlas_texture_id = texture;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
|
||||
free(pixels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_fonts(const char *font_path) {
|
||||
uint font_vbo;
|
||||
uint font_ebo;
|
||||
|
||||
int res = init_freetype(font_path);
|
||||
if (res != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
font_shader = create_shader("shaders/font.vert", "shaders/font.frag");
|
||||
|
||||
glGenVertexArrays(1, &font_vao);
|
||||
glGenBuffers(1, &font_vbo);
|
||||
glGenBuffers(1, &font_instance_vbo);
|
||||
glGenBuffers(1, &font_ebo);
|
||||
|
||||
float quad_vertices[4][2] = {
|
||||
{0.0, 1.0}, // top left
|
||||
{1.0, 1.0}, // top right
|
||||
{1.0, 0.0}, // bottom right
|
||||
{0.0, 0.0}, // bottom left
|
||||
};
|
||||
|
||||
int quad_indices[6] = {
|
||||
0, 1, 2,
|
||||
2, 3, 0
|
||||
};
|
||||
|
||||
glBindVertexArray(font_vao);
|
||||
|
||||
// font quad vbo
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
|
||||
|
||||
// font instance vbo
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)0);
|
||||
glVertexAttribDivisor(1, 1);
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribIPointer(2, 1, GL_INT, sizeof(TextInstance), (void *)sizeof(Vec4f));
|
||||
glVertexAttribDivisor(2, 1);
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) + sizeof(int)));
|
||||
glVertexAttribDivisor(3, 1);
|
||||
glEnableVertexAttribArray(4);
|
||||
glEnableVertexAttribArray(5);
|
||||
glEnableVertexAttribArray(6);
|
||||
glEnableVertexAttribArray(7);
|
||||
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 2 + sizeof(int)));
|
||||
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 3 + sizeof(int)));
|
||||
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 4 + sizeof(int)));
|
||||
glVertexAttribPointer(7, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 5 + sizeof(int)));
|
||||
glVertexAttribDivisor(4, 1);
|
||||
glVertexAttribDivisor(5, 1);
|
||||
glVertexAttribDivisor(6, 1);
|
||||
glVertexAttribDivisor(7, 1);
|
||||
|
||||
// font ebo
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW);
|
||||
|
||||
use_shader(font_shader);
|
||||
|
||||
for (int i = 32; i < 128; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
char text[32];
|
||||
snprintf(text, 32, "texcoords[%d]", (i - 32) * 4 + j);
|
||||
set_vec2f(font_shader, text, zephr_context.font.characters[i].tex_coords[j].x, zephr_context.font.characters[i].tex_coords[j].y);
|
||||
}
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Sizef calculate_text_size(const char *text, int font_size) {
|
||||
float scale = (float)font_size / FONT_PIXEL_SIZE;
|
||||
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 (int i = 0; text[i] != '\0'; i++) {
|
||||
Character ch = zephr_context.font.characters[(uint)text[i]];
|
||||
max_bearing_h = max(max_bearing_h, ch.bearing.height);
|
||||
}
|
||||
|
||||
for (int i = 0; text[i] != '\0'; i++) {
|
||||
Character ch = zephr_context.font.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 draw_text(const char* text, int font_size, UIConstraints constraints, const Color *color, Alignment alignment) {
|
||||
use_shader(font_shader);
|
||||
Color text_color = { 0 };
|
||||
if (color) {
|
||||
text_color = (Color){ color->r / 255.f, color->g / 255.f, color->b / 255.f, color->a / 255.f };
|
||||
}
|
||||
set_mat4f(font_shader, "projection", (float *)zephr_context.projection.m);
|
||||
|
||||
Vec2f pos = { 0.f, 0.f };
|
||||
Sizef size = {0};
|
||||
|
||||
apply_constraints(constraints, &pos, &size);
|
||||
|
||||
Sizef text_size = calculate_text_size(text, FONT_PIXEL_SIZE);
|
||||
float font_scale = (float)font_size / FONT_PIXEL_SIZE * size.width;
|
||||
|
||||
apply_alignment(alignment, &pos, (Sizef){ text_size.width * font_scale, text_size.height * font_scale });
|
||||
|
||||
Matrix4x4 model = identity();
|
||||
apply_scale(&model, (Sizef){font_scale, font_scale});
|
||||
|
||||
// rotate around the center point of the text
|
||||
apply_translation(&model, (Vec2f){-text_size.width * font_scale / 2.f, -text_size.height * font_scale / 2.f});
|
||||
apply_rotation(&model, to_radians(constraints.rotation));
|
||||
apply_translation(&model, (Vec2f){text_size.width *font_scale / 2.f, text_size.height * font_scale / 2.f});
|
||||
|
||||
apply_translation(&model, pos);
|
||||
|
||||
int max_bearing_h = 0;
|
||||
for (int i = 0; (i < text[i]) != '\0'; i++) {
|
||||
Character ch = zephr_context.font.characters[(int)text[i]];
|
||||
max_bearing_h = max(max_bearing_h, ch.bearing.height);
|
||||
}
|
||||
|
||||
float first_char_bearing_w = zephr_context.font.characters[(int)text[0]].bearing.width;
|
||||
|
||||
GlyphInstanceList glyph_instance_list;
|
||||
new_glyph_instance_list(&glyph_instance_list, 16);
|
||||
|
||||
// we use the original text and character sizes in the loop and then we just
|
||||
// scale up or down the model matrix to get the desired font size.
|
||||
// this way everything works out fine and we get to transform the text using the
|
||||
// model matrix
|
||||
int c = 0;
|
||||
int x = 0;
|
||||
while (text[c] != '\0') {
|
||||
Character ch = zephr_context.font.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));
|
||||
float ypos = (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h));
|
||||
|
||||
TextInstance instance = {
|
||||
.position = (Vec4f){xpos, ypos, ch.size.width, ch.size.height},
|
||||
.tex_coords_index = (int)text[c] - 32,
|
||||
.color = text_color,
|
||||
.model = {0},
|
||||
};
|
||||
|
||||
memcpy(instance.model, model.m, sizeof(float[4][4]));
|
||||
|
||||
add_glyph_instance(&glyph_instance_list, instance);
|
||||
|
||||
x += (ch.advance >> 6);
|
||||
c++;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, zephr_context.font.atlas_texture_id);
|
||||
glBindVertexArray(font_vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(TextInstance) * glyph_instance_list.size, glyph_instance_list.data, GL_DYNAMIC_DRAW);
|
||||
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL, glyph_instance_list.size);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
free(glyph_instance_list.data);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui.h"
|
||||
|
||||
typedef struct Character {
|
||||
Size size;
|
||||
Size bearing;
|
||||
unsigned int advance;
|
||||
Vec2f tex_coords[4]; // the 4 corners of the character quad
|
||||
} Character;
|
||||
|
||||
typedef struct ZephFont {
|
||||
Character characters[128];
|
||||
unsigned int atlas_texture_id;
|
||||
} ZephFont;
|
||||
|
||||
typedef struct TextInstance {
|
||||
Vec4f position;
|
||||
int tex_coords_index;
|
||||
Color color;
|
||||
float model[4][4];
|
||||
} TextInstance;
|
||||
|
||||
typedef struct GlyphInstanceList {
|
||||
TextInstance *data;
|
||||
int size;
|
||||
int capacity;
|
||||
} GlyphInstanceList;
|
||||
|
||||
int init_fonts(const char *font_path);
|
||||
Sizef calculate_text_size(const char *text, int font_size);
|
||||
void draw_text(const char* text, int font_size, UIConstraints constraints, const Color *color, Alignment alignment);
|
||||
@@ -1,7 +1,8 @@
|
||||
zephr stuff
|
||||
- "premade" (but also customizable) ui elements (buttons, text boxes, etc) with instanced rendering
|
||||
- border decorations for shapes (maybe. or maybe only for ui elements)
|
||||
- some kind of ui layout/hierarchy system (for later, when we actually need it)
|
||||
- grid texture (draw_image? or draw_texture? func)
|
||||
- texture func (draw_image? or draw_texture? func)
|
||||
|
||||
text rendering improvements
|
||||
- [ ] unicode support (allows for non-ascii characters)
|
||||
|
||||
@@ -1,181 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include <freetype/ftmm.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <glad/glx.h>
|
||||
|
||||
#include "cudoku.h"
|
||||
#include "shader.h"
|
||||
#include "audio.h"
|
||||
#include "ui.h"
|
||||
#include "x11.h"
|
||||
#include "zephr.h"
|
||||
|
||||
#define FONT_PIXEL_SIZE 64
|
||||
|
||||
Display *display;
|
||||
Window window;
|
||||
|
||||
Context zephr_context = {0};
|
||||
Shader font_shader;
|
||||
extern Context zephr_context;
|
||||
Shader ui_shader;
|
||||
unsigned int font_vao;
|
||||
unsigned int ui_vao;
|
||||
unsigned int font_instance_vbo;
|
||||
unsigned int ui_vbo;
|
||||
|
||||
void new_glyph_instance_list(GlyphInstanceList *list, uint capacity) {
|
||||
list->size = 0;
|
||||
list->capacity = capacity;
|
||||
list->data = malloc(list->capacity * sizeof(TextInstance));
|
||||
}
|
||||
|
||||
void clear_glyph_instance_list(GlyphInstanceList *list) {
|
||||
list->size = 0;
|
||||
memset(list->data, 0, list->size * sizeof(TextInstance));
|
||||
}
|
||||
|
||||
void add_glyph_instance(GlyphInstanceList *list, TextInstance instance) {
|
||||
if (list->size >= list->capacity) {
|
||||
list->capacity *= 2;
|
||||
TextInstance* temp = realloc(list->data, list->capacity * sizeof(TextInstance));
|
||||
if (!temp) {
|
||||
printf("[FATAL] Failed to reallocate memory for glyph instance list\n");
|
||||
exit(1);
|
||||
}
|
||||
list->data = temp;
|
||||
}
|
||||
|
||||
list->data[list->size++] = instance;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// sets the variable font to be bold
|
||||
/* 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, FONT_PIXEL_SIZE);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
int pen_x = 0, pen_y = 0;
|
||||
|
||||
/* FT_UInt glyph_idx; */
|
||||
/* FT_ULong c = FT_Get_First_Char(face, &glyph_idx); */
|
||||
|
||||
uint tex_width = 0, tex_height = 0;
|
||||
for (uint i = 32; i < 128; i++) {
|
||||
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
|
||||
printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
|
||||
}
|
||||
|
||||
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
|
||||
|
||||
tex_width += face->glyph->bitmap.width + 1;
|
||||
tex_height = max(tex_height, face->glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
printf("[INFO] Font atlas texture size: %d,%d\n", tex_width, tex_height);
|
||||
|
||||
char *pixels = (char *)calloc(tex_width * tex_height, 1);
|
||||
|
||||
for (uint i = 32; i < 128; i++) {
|
||||
/* while (glyph_idx) { */
|
||||
if (FT_Load_Char(face, i, FT_LOAD_RENDER)) {
|
||||
printf("[ERROR]: failed to load glyph for char '0x%x'\n", i);
|
||||
}
|
||||
|
||||
/* FT_Render_Glyph(face->glyph, FT_RENDER_MODE_SDF); */
|
||||
|
||||
FT_Bitmap *bmp = &face->glyph->bitmap;
|
||||
|
||||
if (pen_x + bmp->width >= tex_width) {
|
||||
pen_x = 0;
|
||||
pen_y += (1 + (face->size->metrics.height >> 6));
|
||||
}
|
||||
|
||||
for (uint row = 0; row < bmp->rows; row++) {
|
||||
for (uint col = 0; col < bmp->width; col++) {
|
||||
int x = pen_x + col;
|
||||
int y = pen_y + row;
|
||||
pixels[y * tex_width + x] = bmp->buffer[row * bmp->pitch + col];
|
||||
}
|
||||
}
|
||||
|
||||
float atlas_x0 = (float)pen_x / (float)tex_width;
|
||||
float atlas_y0 = (float)pen_y / (float)tex_height;
|
||||
float atlas_x1 = (float)(pen_x + bmp->width) / (float)tex_width;
|
||||
float atlas_y1 = (float)(pen_y + bmp->rows) / (float)tex_height;
|
||||
|
||||
Vec2f top_left = (Vec2f){ .x = atlas_x0, .y = atlas_y1 };
|
||||
Vec2f top_right = (Vec2f){ .x = atlas_x1, .y = atlas_y1 };
|
||||
Vec2f bottom_right = (Vec2f){ .x = atlas_x1, .y = atlas_y0 };
|
||||
Vec2f bottom_left = (Vec2f){ .x = atlas_x0, .y = atlas_y0 };
|
||||
|
||||
Character character;
|
||||
|
||||
character.tex_coords[0] = top_left;
|
||||
character.tex_coords[1] = top_right;
|
||||
character.tex_coords[2] = bottom_right;
|
||||
character.tex_coords[3] = bottom_left;
|
||||
character.advance = face->glyph->advance.x;
|
||||
character.size = (Size){ .width = face->glyph->bitmap.width, .height = face->glyph->bitmap.rows };
|
||||
character.bearing = (Size){ .width = face->glyph->bitmap_left, .height = face->glyph->bitmap_top };
|
||||
|
||||
zephr_context.font.characters[i] = character;
|
||||
|
||||
pen_x += bmp->width + 1;
|
||||
|
||||
/* c = FT_Get_Next_Char(face, c, &glyph_idx); */
|
||||
}
|
||||
|
||||
unsigned int texture;
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, tex_width,
|
||||
tex_height, 0, GL_RED, GL_UNSIGNED_BYTE,
|
||||
pixels);
|
||||
|
||||
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);
|
||||
|
||||
zephr_context.font.atlas_texture_id = texture;
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
FT_Done_Face(face);
|
||||
FT_Done_FreeType(ft);
|
||||
|
||||
free(pixels);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_ui(const char* font_path, Size window_size) {
|
||||
uint font_vbo;
|
||||
uint font_ebo;
|
||||
|
||||
zephr_context.window_size = window_size;
|
||||
zephr_context.projection = orthographic_projection_2d(0.f, window_size.width, window_size.height, 0.f);
|
||||
|
||||
@@ -188,74 +28,11 @@ int init_ui(const char* font_path, Size window_size) {
|
||||
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, &font_instance_vbo);
|
||||
glGenBuffers(1, &font_ebo);
|
||||
glGenBuffers(1, &ui_vbo);
|
||||
|
||||
float quad_vertices[4][2] = {
|
||||
{0.0, 1.0}, // top left
|
||||
{1.0, 1.0}, // top right
|
||||
{1.0, 0.0}, // bottom right
|
||||
{0.0, 0.0}, // bottom left
|
||||
};
|
||||
|
||||
int quad_indices[6] = {
|
||||
0, 1, 2,
|
||||
2, 3, 0
|
||||
};
|
||||
|
||||
glBindVertexArray(font_vao);
|
||||
|
||||
// font quad vbo
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(quad_vertices), quad_vertices, GL_STATIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
|
||||
|
||||
// font instance vbo
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo);
|
||||
glEnableVertexAttribArray(1);
|
||||
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)0);
|
||||
glVertexAttribDivisor(1, 1);
|
||||
glEnableVertexAttribArray(2);
|
||||
glVertexAttribIPointer(2, 1, GL_INT, sizeof(TextInstance), (void *)sizeof(Vec4f));
|
||||
glVertexAttribDivisor(2, 1);
|
||||
glEnableVertexAttribArray(3);
|
||||
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) + sizeof(int)));
|
||||
glVertexAttribDivisor(3, 1);
|
||||
glEnableVertexAttribArray(4);
|
||||
glEnableVertexAttribArray(5);
|
||||
glEnableVertexAttribArray(6);
|
||||
glEnableVertexAttribArray(7);
|
||||
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 2 + sizeof(int)));
|
||||
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 3 + sizeof(int)));
|
||||
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 4 + sizeof(int)));
|
||||
glVertexAttribPointer(7, 4, GL_FLOAT, GL_FALSE, sizeof(TextInstance), (void *)(sizeof(Vec4f) * 5 + sizeof(int)));
|
||||
glVertexAttribDivisor(4, 1);
|
||||
glVertexAttribDivisor(5, 1);
|
||||
glVertexAttribDivisor(6, 1);
|
||||
glVertexAttribDivisor(7, 1);
|
||||
|
||||
// font ebo
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(quad_indices), quad_indices, GL_STATIC_DRAW);
|
||||
|
||||
use_shader(font_shader);
|
||||
|
||||
for (int i = 32; i < 128; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
char text[32];
|
||||
snprintf(text, 32, "texcoords[%d]", (i - 32) * 4 + j);
|
||||
set_vec2f(font_shader, text, zephr_context.font.characters[i].tex_coords[j].x, zephr_context.font.characters[i].tex_coords[j].y);
|
||||
}
|
||||
}
|
||||
|
||||
glBindVertexArray(ui_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, ui_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
|
||||
@@ -268,141 +45,6 @@ int init_ui(const char* font_path, Size window_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Has to be called after calling init_zephr()
|
||||
void make_window_non_resizable() {
|
||||
x11_make_window_non_resizable(display, window, zephr_context.window_size.width, zephr_context.window_size.height);
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
get_screen_size(display, &zephr_context.screen_size.width, &zephr_context.screen_size.height);
|
||||
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);
|
||||
}
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
// This MUST be called at the end of the frame
|
||||
void zephr_swap_buffers() {
|
||||
glXSwapBuffers(display, window);
|
||||
}
|
||||
|
||||
Size zephr_get_window_size() {
|
||||
return zephr_context.window_size;
|
||||
}
|
||||
|
||||
Sizef calculate_text_size(const char *text, int font_size) {
|
||||
float scale = (float)font_size / FONT_PIXEL_SIZE;
|
||||
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 (int i = 0; text[i] != '\0'; i++) {
|
||||
Character ch = zephr_context.font.characters[(uint)text[i]];
|
||||
max_bearing_h = max(max_bearing_h, ch.bearing.height);
|
||||
}
|
||||
|
||||
for (int i = 0; text[i] != '\0'; i++) {
|
||||
Character ch = zephr_context.font.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:
|
||||
@@ -620,88 +262,6 @@ void draw_triangle(UIConstraints constraints, const Color *color, Alignment alig
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void draw_text(const char* text, int font_size, UIConstraints constraints, const Color *color, Alignment alignment) {
|
||||
use_shader(font_shader);
|
||||
Color text_color = { 0 };
|
||||
if (color) {
|
||||
text_color = (Color){ color->r / 255.f, color->g / 255.f, color->b / 255.f, color->a / 255.f };
|
||||
}
|
||||
set_mat4f(font_shader, "projection", (float *)zephr_context.projection.m);
|
||||
|
||||
Vec2f pos = { 0.f, 0.f };
|
||||
Sizef size = {0};
|
||||
|
||||
apply_constraints(constraints, &pos, &size);
|
||||
|
||||
Sizef text_size = calculate_text_size(text, FONT_PIXEL_SIZE);
|
||||
float font_scale = (float)font_size / FONT_PIXEL_SIZE * size.width;
|
||||
|
||||
apply_alignment(alignment, &pos, (Sizef){ text_size.width * font_scale, text_size.height * font_scale });
|
||||
|
||||
Matrix4x4 model = identity();
|
||||
apply_scale(&model, (Sizef){font_scale, font_scale});
|
||||
|
||||
// rotate around the center point of the text
|
||||
apply_translation(&model, (Vec2f){-text_size.width * font_scale / 2.f, -text_size.height * font_scale / 2.f});
|
||||
apply_rotation(&model, to_radians(constraints.rotation));
|
||||
apply_translation(&model, (Vec2f){text_size.width *font_scale / 2.f, text_size.height * font_scale / 2.f});
|
||||
|
||||
apply_translation(&model, pos);
|
||||
|
||||
int max_bearing_h = 0;
|
||||
for (int i = 0; (i < text[i]) != '\0'; i++) {
|
||||
Character ch = zephr_context.font.characters[(int)text[i]];
|
||||
max_bearing_h = max(max_bearing_h, ch.bearing.height);
|
||||
}
|
||||
|
||||
float first_char_bearing_w = zephr_context.font.characters[(int)text[0]].bearing.width;
|
||||
|
||||
GlyphInstanceList glyph_instance_list;
|
||||
new_glyph_instance_list(&glyph_instance_list, 16);
|
||||
|
||||
// we use the original text and character sizes in the loop and then we just
|
||||
// scale up or down the model matrix to get the desired font size.
|
||||
// this way everything works out fine and we get to transform the text using the
|
||||
// model matrix
|
||||
int c = 0;
|
||||
int x = 0;
|
||||
while (text[c] != '\0') {
|
||||
Character ch = zephr_context.font.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));
|
||||
float ypos = (text_size.height - ch.bearing.height - (text_size.height - max_bearing_h));
|
||||
|
||||
TextInstance instance = {
|
||||
.position = (Vec4f){xpos, ypos, ch.size.width, ch.size.height},
|
||||
.tex_coords_index = (int)text[c] - 32,
|
||||
.color = text_color,
|
||||
.model = {0},
|
||||
};
|
||||
|
||||
memcpy(instance.model, model.m, sizeof(float[4][4]));
|
||||
|
||||
add_glyph_instance(&glyph_instance_list, instance);
|
||||
|
||||
x += (ch.advance >> 6);
|
||||
c++;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, zephr_context.font.atlas_texture_id);
|
||||
glBindVertexArray(font_vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, font_instance_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(TextInstance) * glyph_instance_list.size, glyph_instance_list.data, GL_DYNAMIC_DRAW);
|
||||
|
||||
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL, glyph_instance_list.size);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
free(glyph_instance_list.data);
|
||||
}
|
||||
|
||||
/* enum Element { */
|
||||
/* Rect, */
|
||||
/* Circle, */
|
||||
|
||||
@@ -3,42 +3,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "z_math.h"
|
||||
|
||||
typedef struct Character {
|
||||
Size size;
|
||||
Size bearing;
|
||||
unsigned int advance;
|
||||
Vec2f tex_coords[4]; // the 4 corners of the character quad
|
||||
} Character;
|
||||
|
||||
typedef struct ZephFont {
|
||||
Character characters[128];
|
||||
unsigned int atlas_texture_id;
|
||||
} ZephFont;
|
||||
|
||||
typedef struct TextInstance {
|
||||
Vec4f position;
|
||||
int tex_coords_index;
|
||||
Color color;
|
||||
float model[4][4];
|
||||
} TextInstance;
|
||||
|
||||
typedef struct GlyphInstanceList {
|
||||
TextInstance *data;
|
||||
int size;
|
||||
int capacity;
|
||||
} GlyphInstanceList;
|
||||
|
||||
typedef struct Context {
|
||||
bool should_quit;
|
||||
Color clear_color;
|
||||
Size screen_size;
|
||||
Size window_size;
|
||||
ZephFont font;
|
||||
|
||||
Matrix4x4 projection;
|
||||
} Context;
|
||||
#include "zephr_math.h"
|
||||
|
||||
typedef enum Alignment {
|
||||
ALIGN_TOP_LEFT,
|
||||
@@ -67,20 +32,14 @@ typedef struct UIConstraints {
|
||||
float rotation;
|
||||
} UIConstraints;
|
||||
|
||||
int init_zephr(const char* font_path, const char* window_title, Size window_size, Color *clear_color);
|
||||
void deinit_zephr();
|
||||
void make_window_non_resizable();
|
||||
bool zephr_should_quit();
|
||||
void zephr_swap_buffers();
|
||||
Size zephr_get_window_size();
|
||||
void zephr_batch_text_draw();
|
||||
int init_ui(const char* font_path, Size 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 set_rotation_constraint(UIConstraints *constraints, float angle_d);
|
||||
void apply_constraints(UIConstraints constraints, Vec2f *pos, Sizef *size);
|
||||
void apply_alignment(Alignment align, Vec2f *pos, Sizef size);
|
||||
void draw_quad(UIConstraints constraints, const Color *color, float border_radius, Alignment align);
|
||||
void draw_circle(UIConstraints constraints, const Color *color, Alignment align);
|
||||
void draw_triangle(UIConstraints constraints, const Color *color, Alignment align);
|
||||
void draw_text(const char* text, int font_size, UIConstraints constraints, const Color *color, Alignment align);
|
||||
Sizef calculate_text_size(const char *text, int font_size);
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
float m[4][4];
|
||||
} Matrix4x4;
|
||||
|
||||
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 Vec4f {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} Vec4f;
|
||||
|
||||
typedef struct Color {
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
} Color;
|
||||
@@ -0,0 +1,111 @@
|
||||
#include <stdio.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <glad/glx.h>
|
||||
|
||||
#include "zephr.h"
|
||||
#include "audio.h"
|
||||
#include "x11.h"
|
||||
#include "ui.h"
|
||||
#include "timer.h"
|
||||
|
||||
Display *display;
|
||||
Window window;
|
||||
|
||||
Context zephr_context = {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};
|
||||
}
|
||||
|
||||
get_screen_size(display, &zephr_context.screen_size.width, &zephr_context.screen_size.height);
|
||||
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);
|
||||
}
|
||||
|
||||
} 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;
|
||||
}
|
||||
|
||||
// This MUST be called at the end of the frame
|
||||
void zephr_swap_buffers() {
|
||||
glXSwapBuffers(display, window);
|
||||
}
|
||||
|
||||
Size zephr_get_window_size() {
|
||||
return zephr_context.window_size;
|
||||
}
|
||||
|
||||
// Has to be called after calling init_zephr()
|
||||
void zephr_make_window_non_resizable() {
|
||||
x11_make_window_non_resizable(display, window, zephr_context.window_size.width, zephr_context.window_size.height);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "text.h"
|
||||
|
||||
typedef struct Context {
|
||||
bool should_quit;
|
||||
Color clear_color;
|
||||
Size screen_size;
|
||||
Size window_size;
|
||||
ZephFont font;
|
||||
|
||||
Matrix4x4 projection;
|
||||
} Context;
|
||||
|
||||
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 zephr_make_window_non_resizable();
|
||||
+36
-35
@@ -1,6 +1,41 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "helper.h"
|
||||
#include "zephr_math.h"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
float to_radians(float degrees) {
|
||||
return degrees * (M_PI / 180.f);
|
||||
}
|
||||
|
||||
Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, float top) {
|
||||
Matrix4x4 result = identity();
|
||||
|
||||
result.m[0][0] = 2.0f / (right - left);
|
||||
result.m[3][0] = -(right + left) / (right - left);
|
||||
result.m[1][1] = 2.0f / (top - bottom);
|
||||
result.m[3][1] = -(top + bottom) / (top - bottom);
|
||||
result.m[2][2] = -1.0f;
|
||||
result.m[3][3] = 1.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Matrix4x4 identity() {
|
||||
Matrix4x4 result = {0};
|
||||
@@ -57,37 +92,3 @@ void apply_translation(Matrix4x4 *matrix, Vec2f pos) {
|
||||
mult_4x4(matrix, &temp);
|
||||
}
|
||||
|
||||
Matrix4x4 orthographic_projection_2d(float left, float right, float bottom, float top) {
|
||||
Matrix4x4 result = identity();
|
||||
|
||||
result.m[0][0] = 2.0f / (right - left);
|
||||
result.m[3][0] = -(right + left) / (right - left);
|
||||
result.m[1][1] = 2.0f / (top - bottom);
|
||||
result.m[3][1] = -(top + bottom) / (top - bottom);
|
||||
result.m[2][2] = -1.0f;
|
||||
result.m[3][3] = 1.0f;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
float to_radians(float degrees) {
|
||||
return degrees * (M_PI / 180.f);
|
||||
}
|
||||
+40
-6
@@ -1,16 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdlib.h>
|
||||
typedef struct {
|
||||
float m[4][4];
|
||||
} Matrix4x4;
|
||||
|
||||
#include "z_math.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 Vec4f {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float w;
|
||||
} Vec4f;
|
||||
|
||||
typedef struct Color {
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
} Color;
|
||||
|
||||
int max(int a, int b);
|
||||
float maxf(float a, float b);
|
||||
float clampf(float value, float min, float max);
|
||||
float to_radians(float degrees);
|
||||
Matrix4x4 identity();
|
||||
void mult_4x4(Matrix4x4 *multiplicant, Matrix4x4 *multiplier);
|
||||
void apply_scale(Matrix4x4 *matrix, Sizef scale);
|
||||
void apply_rotation(Matrix4x4 *model, float angle);
|
||||
void apply_translation(Matrix4x4 *matrix, Vec2f pos);
|
||||
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);
|
||||
float to_radians(float degrees);
|
||||
Reference in New Issue
Block a user