Entropie git · main
Entropie festival game; first steps
C 88.5% Markdown 9.1% GLSL 2.3%git clone https://git.christianimmanuel.de/games/Entropie.gitwget https://git.christianimmanuel.de/games/Entropie/archive/Entropie.tar.gzentropie/src/render/ui.c raw
#include "render/ui.h"
#include "core/platform.h"
#include "render/shader.h"
#include <string.h>
#include <SDL3/SDL.h>
#include "math/linalg.h"
#define UI_MARGIN 15.0f
bool Ui_init(Ui *ui, const char *font_asset, float font_size) {
memset(ui, 0, sizeof(*ui));
if (!TTF_Init()) {
SDL_Log("TTF_Init failed: %s", SDL_GetError());
return false;
}
ui->ttf_ready = true;
char path[1024];
if (!Platform_assetPath(font_asset, path, sizeof(path)) ||
!(ui->font = TTF_OpenFont(path, font_size))) {
SDL_Log("Cannot open font %s: %s", font_asset, SDL_GetError());
Ui_shutdown(ui);
return false;
}
ui->program = Shader_load("shaders/ui.vert", "shaders/ui.frag");
if (!ui->program) {
Ui_shutdown(ui);
return false;
}
ui->u_screen_size = glGetUniformLocation(ui->program, "screenSize");
ui->u_tex = glGetUniformLocation(ui->program, "tex");
ui->u_text_color = glGetUniformLocation(ui->program, "textColor");
ui->u_mode = glGetUniformLocation(ui->program, "mode");
ui->u_rect_size = glGetUniformLocation(ui->program, "rectSize");
ui->u_boil = glGetUniformLocation(ui->program, "boilTime");
ui->u_rim = glGetUniformLocation(ui->program, "rim");
glGenVertexArrays(1, &ui->vao);
glGenBuffers(1, &ui->vbo);
glBindVertexArray(ui->vao);
glBindBuffer(GL_ARRAY_BUFFER, ui->vbo);
glBufferData(GL_ARRAY_BUFFER, 6 * 4 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)(2 * sizeof(float)));
glEnableVertexAttribArray(1);
glBindVertexArray(0);
for (int i = 0; i < UI_LABELS; i++)
glGenTextures(1, &ui->labels[i].texture);
return true;
}
void Ui_shutdown(Ui *ui) {
for (int i = 0; i < UI_LABELS; i++)
if (ui->labels[i].texture) glDeleteTextures(1, &ui->labels[i].texture);
if (ui->vbo) glDeleteBuffers(1, &ui->vbo);
if (ui->vao) glDeleteVertexArrays(1, &ui->vao);
Shader_destroy(&ui->program);
if (ui->font) TTF_CloseFont(ui->font);
if (ui->ttf_ready) TTF_Quit();
memset(ui, 0, sizeof(*ui));
}
/* Renders text into the texture. Only called when the text changes. */
static bool uploadText(Ui *ui, UiLabel *label, const char *text) {
const SDL_Color white = { 255, 255, 255, 255 }; /* coverage ends up in alpha */
SDL_Surface *rendered = TTF_RenderText_Blended(ui->font, text, 0, white);
if (!rendered) return false;
SDL_Surface *rgba = SDL_ConvertSurface(rendered, SDL_PIXELFORMAT_RGBA32);
SDL_DestroySurface(rendered);
if (!rgba) return false;
glBindTexture(GL_TEXTURE_2D, label->texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ROW_LENGTH, rgba->pitch / 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rgba->w, rgba->h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, rgba->pixels);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, 0);
label->width = rgba->w;
label->height = rgba->h;
SDL_DestroySurface(rgba);
return true;
}
/* Label with its texture up to date, or NULL if there is nothing to draw. */
static UiLabel *prepare(Ui *ui, int index, const char *text) {
if (index < 0 || index >= UI_LABELS) return NULL;
UiLabel *label = &ui->labels[index];
if (strcmp(text, label->text) != 0) {
if (!uploadText(ui, label, text)) return NULL;
SDL_strlcpy(label->text, text, sizeof(label->text));
}
return label->width > 0 ? label : NULL;
}
/* Ink and paper */
static const float INK[3] = { 0.10f, 0.07f, 0.14f };
static const float PAPER[3] = { 0.95f, 0.90f, 0.78f };
void Ui_setTime(Ui *ui, float seconds) {
ui->boil_time = floorf(seconds * 12.0f) / 12.0f; /* "on twos", like the pictures */
}
/* A textured (mode 0) or painted (mode 1) quad. */
static void drawQuad(Ui *ui, GLuint texture, float x, float y, float w, float h, int mode,
const float color[4], float rim, int screen_w, int screen_h) {
const float quad[] = {
x, y, 0.0f, 1.0f,
x + w, y, 1.0f, 1.0f,
x + w, y + h, 1.0f, 0.0f,
x, y, 0.0f, 1.0f,
x + w, y + h, 1.0f, 0.0f,
x, y + h, 0.0f, 0.0f,
};
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glUseProgram(ui->program);
glUniform2f(ui->u_screen_size, (float)screen_w, (float)screen_h);
glUniform4f(ui->u_text_color, color[0], color[1], color[2], color[3]);
glUniform1i(ui->u_mode, mode);
glUniform2f(ui->u_rect_size, w, h);
glUniform1f(ui->u_boil, ui->boil_time);
glUniform1f(ui->u_rim, rim);
glUniform1i(ui->u_tex, 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(ui->vao);
glBindBuffer(GL_ARRAY_BUFFER, ui->vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_DYNAMIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
}
static void drawLabelColored(Ui *ui, const UiLabel *label, float x, float y, int screen_w, int screen_h,
const float rgb[3], float alpha) {
const float color[4] = { rgb[0], rgb[1], rgb[2], alpha };
drawQuad(ui, label->texture, x, y, (float)label->width, (float)label->height, 0, color, 0.0f, screen_w, screen_h);
}
/* Ink letters, gone over twice a hair apart so the strokes are a bit fuller. */
static void drawLabel(Ui *ui, const UiLabel *label, float x, float y, int screen_w, int screen_h) {
drawLabelColored(ui, label, x, y, screen_w, screen_h, INK, 1.0f);
drawLabelColored(ui, label, x + 0.6f, y, screen_w, screen_h, INK, 1.0f);
}
void Ui_drawPatch(Ui *ui, float x, float y, float w, float h, float r, float g, float b, float a,
float rim, int screen_w, int screen_h) {
const float color[4] = { r, g, b, a };
drawQuad(ui, ui->labels[0].texture, x, y, w, h, 1, color, rim, screen_w, screen_h);
}
void Ui_drawTextLeft(Ui *ui, int index, const char *text, float x, float y, int screen_w, int screen_h) {
const UiLabel *label = prepare(ui, index, text);
if (label) drawLabel(ui, label, x, y, screen_w, screen_h);
}
/* Ink letters on a faint, thin paper glow: just enough to read them on dark
paint, never wider than the strokes themselves. */
static void drawWithHalo(Ui *ui, const UiLabel *label, float x, float y, int screen_w, int screen_h) {
static const float OFFSETS[][2] = { { -0.8f, -0.8f }, { 0.8f, 0.8f }, { -0.8f, 0.8f }, { 0.8f, -0.8f } };
for (unsigned k = 0; k < sizeof(OFFSETS) / sizeof(OFFSETS[0]); k++)
drawLabelColored(ui, label, x + OFFSETS[k][0], y + OFFSETS[k][1], screen_w, screen_h, PAPER, 0.45f);
drawLabel(ui, label, x, y, screen_w, screen_h);
}
void Ui_drawTextAt(Ui *ui, int index, const char *text, float x, float y, int screen_w, int screen_h) {
const UiLabel *label = prepare(ui, index, text);
if (!label) return;
float left = clampf(x - (float)label->width * 0.5f, UI_MARGIN, (float)screen_w - (float)label->width - UI_MARGIN);
drawWithHalo(ui, label, left, y, screen_w, screen_h);
}
void Ui_drawText(Ui *ui, int index, const char *text, UiCorner corner, int screen_w, int screen_h) {
const UiLabel *label = prepare(ui, index, text);
if (!label) return;
const float w = (float)label->width, h = (float)label->height;
float x = corner == UI_TOP_RIGHT ? (float)screen_w - w - UI_MARGIN : UI_MARGIN;
float y = (float)screen_h - h - UI_MARGIN;
if (corner == UI_CENTER) { /* a bit below the middle of the screen */
x = ((float)screen_w - w) * 0.5f;
y = (float)screen_h * 0.35f;
}
drawWithHalo(ui, label, x, y, screen_w, screen_h);
}
void Ui_drawTextLine(Ui *ui, int index, const char *text, int line, UiCorner side, int screen_w, int screen_h) {
const UiLabel *label = prepare(ui, index, text);
if (!label) return;
const float h = (float)label->height;
if (side == UI_BOTTOM_LEFT) {
drawWithHalo(ui, label, UI_MARGIN, UI_MARGIN + h * 1.1f * (float)line, screen_w, screen_h);
return;
}
float x = side == UI_TOP_RIGHT ? (float)screen_w - (float)label->width - UI_MARGIN : UI_MARGIN;
drawWithHalo(ui, label, x, (float)screen_h - UI_MARGIN - h * 1.1f * (float)(line + 1), screen_w, screen_h);
}