diff --git a/assets/fonts/KodeMono.ttf b/assets/fonts/KodeMono.ttf new file mode 100644 index 0000000..07da589 Binary files /dev/null and b/assets/fonts/KodeMono.ttf differ diff --git a/assets/shaders/text.fragment.glsl b/assets/shaders/text.fragment.glsl new file mode 100644 index 0000000..7bb77e9 --- /dev/null +++ b/assets/shaders/text.fragment.glsl @@ -0,0 +1,11 @@ +#version 330 core +out vec4 pixel_color; + +in vec4 frag_color; +in vec2 frag_uv_position; + +uniform sampler2D glyph_atlas; + +void main() { + pixel_color = vec4(1,1,1,texture(glyph_atlas, frag_uv_position).r); +}; diff --git a/assets/shaders/text.vertex.glsl b/assets/shaders/text.vertex.glsl new file mode 100644 index 0000000..6a344eb --- /dev/null +++ b/assets/shaders/text.vertex.glsl @@ -0,0 +1,73 @@ +#version 330 core +layout (location = 0) in vec2 begin; +layout (location = 1) in int glyph; +layout (location = 2) in float lineHeight; +layout (location = 3) in vec4 color; + +/* +typedef struct GlyphMeta GlyphMeta; +struct GlyphMeta { + // chunk 1 + RLVector2 uv0; + RLVector2 uv1; + + // chunk 2 + real32 xOffset; + real32 yOffset; + real32 width; + real32 height; +}; +*/ + +uniform samplerBuffer glyph_table; + +uniform mat4 projection; +uniform sampler2D font; + +out vec4 frag_color; +out vec2 frag_uv_position; + +const vec2 rectangle_vertices[4] = vec2[]( + vec2(-1, -1), // bl + vec2(-1, 1), // tl + vec2( 1, -1), // br + vec2( 1, 1) // tr +); + +const vec2 uv0_vertices[4] = vec2[]( + vec2(1, 0), // bl + vec2(1, 1), // tl + vec2(0, 0), // br + vec2(0, 1) // tr +); + +const vec2 uv1_vertices[4] = vec2[]( + vec2(0, 1), + vec2(0, 0), + vec2(1, 1), + vec2(1, 0) +); + +void main() { + vec4 chunk1 = texelFetch(glyph_table, glyph * 2 + 0); + vec4 chunk2 = texelFetch(glyph_table, glyph * 2 + 1); + + vec2 uv0 = chunk1.xy; + vec2 uv1 = chunk1.zw; + vec2 offset = chunk2.xy; + vec2 dims = chunk2.zw; + + vec2 p0 = begin + offset*lineHeight; + vec2 p1 = begin + (offset + dims)*lineHeight; + + vec2 dest_half_size = (p1 - p0) / 2; + vec2 dest_center = (p1 + p0) / 2; + vec2 dest_position = rectangle_vertices[gl_VertexID] * dest_half_size + dest_center; + + vec2 uv_position = uv0 * uv0_vertices[gl_VertexID] + uv1 * uv1_vertices[gl_VertexID]; + + gl_Position = projection * vec4(dest_position, 0, 1); + + frag_color = color; + frag_uv_position = vec2(uv_position.x, uv_position.y); +} diff --git a/build b/build index 4fbd9f8..e2bd750 100755 --- a/build +++ b/build @@ -7,7 +7,7 @@ echo [Building target] if [ $DEBUG ]; then time clang -O0 -g -g2 $COMMON_FLAGS -DDJSTDLIB_DEBUG=1 ./src/main.c -o ./target/somaesque $LIB_INCLUDE else - time clang -O3 $COMMON_FLAGS ./src/main.c -o ./target/somaesque $LIB_INCLUDE + time clang -O2 $COMMON_FLAGS ./src/main.c -o ./target/somaesque $LIB_INCLUDE fi echo [Target built] diff --git a/src/common.h b/src/common.h new file mode 100644 index 0000000..1abe185 --- /dev/null +++ b/src/common.h @@ -0,0 +1,12 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "lib/djstdlib/core.h" +#include "lib/raymath.h" + +DefineList(RLVector2, RLVec2); +DefineList(RLVector4, RLVec4); +DefineList(real32, Float); +DefineList(uint32, UInt32); + +#endif diff --git a/src/gfx/Mesh.c b/src/gfx/Mesh.c index 0117b5a..473c454 100644 --- a/src/gfx/Mesh.c +++ b/src/gfx/Mesh.c @@ -23,14 +23,14 @@ Mesh createMesh(const char* obj_file) { size_t num_materials; int success = tinyobj_parse_obj( - &attrib, - &shapes, - &num_shapes, - &materials, - &num_materials, - obj_file, - tinyobj_get_filedata, - (void *)temp.arena, + &attrib, + &shapes, + &num_shapes, + &materials, + &num_materials, + obj_file, + tinyobj_get_filedata, + (void *)temp.arena, TINYOBJ_FLAG_TRIANGULATE ); diff --git a/src/gfx/Shader.c b/src/gfx/Shader.c index bfcc3ec..5d40cbf 100644 --- a/src/gfx/Shader.c +++ b/src/gfx/Shader.c @@ -83,6 +83,13 @@ void setUniform3fvByLoc(int uniformLocation, RLVector3 *vector) { glUniform3fv(uniformLocation, 1, Vector3ToFloat(*vector)); } +void setUniform1i(Shader *s, const char *uniformName, int32 i) { + glUniform1i(glGetUniformLocation(s->progId, uniformName), (GLint)i); +} +void setUniform1iByLoc(int uniformLocation, int32 i) { + glUniform1i(uniformLocation, (GLint)i); +} + void setUniform2fv(Shader *s, const char *uniformName, RLVector2 *vector) { glUniform2fv(glGetUniformLocation(s->progId, uniformName), 1, (const GLfloat *)vector); } diff --git a/src/gfx/Shader.h b/src/gfx/Shader.h index d471c1a..4bbb20d 100644 --- a/src/gfx/Shader.h +++ b/src/gfx/Shader.h @@ -22,6 +22,9 @@ void setUniform3fvByLoc(int uniformLocation, RLVector3 *vector); void setUniform2fv(Shader *s, const char *uniformName, RLVector2 *vector); void setUniform2fvByLoc(int uniformLocation, RLVector2 *vector); +void setUniform1i(Shader *s, const char *uniformName, int32 i); +void setUniform1iByLoc(int uniformLocation, int32 i); + int getUniformLocation(Shader *s, const char *uniformName); #endif diff --git a/src/gfx/Texture.c b/src/gfx/Texture.c index d9444b4..d4fec4b 100644 --- a/src/gfx/Texture.c +++ b/src/gfx/Texture.c @@ -1,9 +1,88 @@ #include "Texture.h" -#include "../lib/loaders/stb_image.h" -#include "../lib/glad/glad.h" -#include "../lib/djstdlib/core.h" -Texture createTexture(const char* source_path) { +Texture createTexture(const char* bitmap, int32 width, int32 height) { + Texture result = {0}; + result.width = width; + result.height = height; + glGenTextures(1, &result.tex_id); + glBindTexture(GL_TEXTURE_2D, result.tex_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, result.width, result.height, 0, GL_RGB, GL_UNSIGNED_BYTE, bitmap); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glGenerateMipmap(GL_TEXTURE_2D); + + return result; +} + +DefineList(stbtt_bakedchar, STBBakedChar); + +Font createFont(Arena *arena, string ttfLocation, real32 lineHeight) { + const int CODEPOINT_START = 32; + const int CODEPOINT_END = 126; + + int32 atlasWidth = 16 * lineHeight; + int32 atlasHeight = 6 * lineHeight; + + STBBakedCharList bakedCharlist = PushFullListZero(arena, STBBakedCharList, CODEPOINT_END - CODEPOINT_START); + string fontFile = os_readEntireFile(arena, ttfLocation); + + char *bakedFontBitmap = PushArrayZero(arena, char, atlasWidth*atlasHeight); + int32 bake_result = stbtt_BakeFontBitmap( + (unsigned char *)fontFile.str, 0, + lineHeight, + (unsigned char *)bakedFontBitmap, atlasWidth, atlasHeight, + CODEPOINT_START, CODEPOINT_END - CODEPOINT_START, + bakedCharlist.data); + + GlyphMetaList glyphMeta = PushFullListZero(arena, GlyphMetaList, bakedCharlist.length); + real32 x, y; + real32 lastX, lastY; + for (EachIn(bakedCharlist, i)) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(bakedCharlist.data, atlasWidth, atlasHeight, i, &x, &y, &q, 1); + glyphMeta.data[i] = (GlyphMeta){ + .uv0=(RLVector2){q.s0, q.t1}, + .uv1=(RLVector2){q.s1, q.t0}, + .xOffset=q.x0 - lastX, + .yOffset=q.y0 + lineHeight, + .width=q.x1-q.x0, + .height=q.y1-q.y0, + }; + lastX = x; + lastY = y; + } + + Font result = {0}; + result.glyphMeta = glyphMeta; + result.lineHeight = lineHeight; + result.charWidth = bakedCharlist.data[0].xadvance; + + glGenTextures(1, &result.texId); + glBindTexture(GL_TEXTURE_2D, result.texId); + //glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, atlasWidth, atlasHeight, 0, GL_RED, GL_UNSIGNED_BYTE, bakedFontBitmap); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glGenerateMipmap(GL_TEXTURE_2D); + + glGenBuffers(1, &result.glyphTableBufId); + glBindBuffer(GL_TEXTURE_BUFFER, result.glyphTableBufId); + glBufferData(GL_TEXTURE_BUFFER, sizeof(result.glyphMeta.data[0])*result.glyphMeta.length, result.glyphMeta.data, GL_STATIC_DRAW); + + glGenTextures(1, &result.glyphTableTexId); + glBindTexture(GL_TEXTURE_BUFFER, result.glyphTableTexId); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, result.glyphTableBufId); + + return result; +} + +Texture createTextureFromFile(const char* source_path) { Texture result = {0}; glGenTextures(1, &result.tex_id); glBindTexture(GL_TEXTURE_2D, result.tex_id); diff --git a/src/gfx/Texture.h b/src/gfx/Texture.h index 153af12..c8fdc41 100644 --- a/src/gfx/Texture.h +++ b/src/gfx/Texture.h @@ -1,12 +1,45 @@ #ifndef LEDDA_TEXTURE_H #define LEDDA_TEXTURE_H -typedef struct { - unsigned int tex_id; - int width; - int height; -} Texture; +#include "../lib/djstdlib/core.h" +#include "../lib/loaders/stb_truetype.h" +#include "../lib/loaders/stb_image.h" +#include "../lib/glad/glad.h" +#include "../lib/djstdlib/core.h" +#include "../lib/djstdlib/os.h" +#include "../common.h" -Texture createTexture(const char* source_path); +typedef struct Texture Texture; +struct Texture { + uint32 tex_id; + int32 width; + int32 height; +}; + +typedef struct GlyphMeta GlyphMeta; +struct GlyphMeta { + RLVector2 uv0; + RLVector2 uv1; + real32 xOffset; + real32 yOffset; + real32 width; + real32 height; +}; + +DefineList(GlyphMeta, GlyphMeta); + +typedef struct Font Font; +struct Font { + GlyphMetaList glyphMeta; + real32 lineHeight; + real32 charWidth; + uint32 texId; + uint32 glyphTableTexId; + uint32 glyphTableBufId; +}; + +Texture createTexture(const char* bitmap, int32 width, int32 height); +Texture createTextureFromFile(const char* source_path); +Font createFont(Arena *arena, string ttfLocation, real32 lineHeight); #endif diff --git a/src/main.c b/src/main.c index ce7041d..d92dfc8 100644 --- a/src/main.c +++ b/src/main.c @@ -1,10 +1,7 @@ -#include "lib/djstdlib/core.h" -#define _POSIX_C_SOURCE 199309L -#include "time.h" - // Library initialisation #define RAYMATH_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION +#define STB_TRUETYPE_IMPLEMENTATION #define TINYOBJ_LOADER_C_IMPLEMENTATION // Project @@ -67,6 +64,7 @@ struct SomaState { RLVector3 rotAxisX; RLVector3 rotAxisY; UI_Rect *threedeePaneRect; + real64 explosionOffset; }; typedef struct Soma Soma; @@ -209,17 +207,12 @@ static void advanceDisplayReverse(Soma *soma) { static void advanceDisplay(Soma *soma) { if (soma->state.displayingSolutions) { - if (soma->state.displayedSolution == soma->solutions.length - 1) { - soma->state.displayedSolution = 0; - } else { - soma->state.displayedSolution += 1; - } + soma->state.explosionOffset = 0; + soma->state.displayedSolution += 1; + soma->state.displayedSolution %= soma->solutions.length; } else { - if (soma->state.displayedPolycube == soma->polycubeInput.length - 1) { - soma->state.displayedPolycube = 0; - } else { - soma->state.displayedPolycube += 1; - } + soma->state.displayedPolycube += 1; + soma->state.displayedPolycube %= soma->polycubes.length; } } @@ -281,6 +274,11 @@ void processInput(Soma *soma) { if (soma->state.displayingSolutions) { real64 scrollDelta = soma->ui->prevInput->mouse.scroll.dY; if (scrollDelta > 0.001 || scrollDelta < -0.001) { + soma->state.explosionOffset += scrollDelta; + if (soma->state.explosionOffset > 0) { + scrollDelta -= soma->state.explosionOffset; + soma->state.explosionOffset = 0; + } SceneGraphNode *rootNode = getSceneGraphNode(soma->scene, soma->solutionNode); int32 nextChildHandle = rootNode->firstChild; while (nextChildHandle) { @@ -335,45 +333,47 @@ static void ui_Interaction(UI_Context *ui, Soma *soma) { real32 padding = 20; real32 childGap = 5; - UI(ui, .childGap=padding, .flags=UI_Flag_Vertical) { - if (soma->state.displayingSolutions && soma->solutions.length > 0) { - SomaSolution *currentSolution = &soma->solutions.data[soma->state.displayedSolution]; - for (int x = 0; x < soma->puzzleDims[0]; x++) UI(ui, .childGap=childGap, .flags=UI_Flag_Vertical) { - for (int y = 0; y < soma->puzzleDims[1]; y++) UI(ui, .childGap=childGap) { - for (int z = 0; z < soma->puzzleDims[2]; z++) { - for (EachIn(*currentSolution, i)) { - uint64 spaceRepr = currentSolution->data[i]; - bool cellActive = filledAt(&(VoxelSpace){ - .space = spaceRepr, - .dim_x = soma->puzzleDims[0], - .dim_y = soma->puzzleDims[1], - .dim_z = soma->puzzleDims[2], - }, x, y, z); - if (cellActive) UI(ui, - .width=boxSize, - .height=boxSize, - .color=soma->polycubeInput.data[i].color, - .borderRadius=5, - .borderThickness=0, - ); + UI(.flags=UI_Flag_Center | UI_Flag_HeightGrow) { + UI(.childGap=padding, .flags=UI_Flag_Vertical | UI_Flag_Center) { + if (soma->state.displayingSolutions && soma->solutions.length > 0) { + SomaSolution *currentSolution = &soma->solutions.data[soma->state.displayedSolution]; + for (int x = 0; x < soma->puzzleDims[0]; x++) UI(.childGap=childGap, .flags=UI_Flag_Vertical) { + for (int y = 0; y < soma->puzzleDims[1]; y++) UI(.childGap=childGap) { + for (int z = 0; z < soma->puzzleDims[2]; z++) { + for (EachIn(*currentSolution, i)) { + uint64 spaceRepr = currentSolution->data[i]; + bool cellActive = filledAt(&(VoxelSpace){ + .space = spaceRepr, + .dim_x = soma->puzzleDims[0], + .dim_y = soma->puzzleDims[1], + .dim_z = soma->puzzleDims[2], + }, x, y, z); + if (cellActive) UI( + .width=boxSize, + .height=boxSize, + .color=soma->polycubeInput.data[i].color, + .borderRadius=5, + .borderThickness=0, + ); + } } } } - } - } else { - PolycubeInput *currentPolycube = &soma->polycubeInput.data[soma->state.displayedPolycube]; - for (int x = 0; x < currentPolycube->repr.dim_x; x++) UI(ui, .childGap=childGap, .flags=UI_Flag_Vertical) { - for (int y = 0; y < currentPolycube->repr.dim_y; y++) UI(ui, .childGap=childGap) { - for (int z = 0; z < currentPolycube->repr.dim_z; z++) { - bool cellActive = filledAt(¤tPolycube->repr, x, y, z); - if (ui_CheckboxRect(ui, &cellActive, UI_CreateRect( - .width = boxSize, - .height = boxSize, - .borderRadius = 2, - .color = currentPolycube->color, - ))) { - soma->state.polycubeDirty = true; - spaceSet(¤tPolycube->repr, cellActive, x, y, z); + } else { + PolycubeInput *currentPolycube = &soma->polycubeInput.data[soma->state.displayedPolycube]; + for (int x = 0; x < currentPolycube->repr.dim_x; x++) UI(.childGap=childGap, .flags=UI_Flag_Vertical) { + for (int y = 0; y < currentPolycube->repr.dim_y; y++) UI(.childGap=childGap) { + for (int z = 0; z < currentPolycube->repr.dim_z; z++) { + bool cellActive = filledAt(¤tPolycube->repr, x, y, z); + if (ui_CheckboxRect(ui, &cellActive, UI_CreateRect( + .width = boxSize, + .height = boxSize, + .borderRadius = 2, + .color = currentPolycube->color, + ))) { + soma->state.polycubeDirty = true; + spaceSet(¤tPolycube->repr, cellActive, x, y, z); + } } } } @@ -383,39 +383,41 @@ static void ui_Interaction(UI_Context *ui, Soma *soma) { } static void DJUI_Soma(UI_Context *ui, Soma *soma) { - UI(ui, .flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow) { - UI(ui, .padding=UI_PadUniform(10), .color={0.2, 0.2, 0.2, 1}, .flags=UI_Flag_HeightGrow) { + RLVector4 darkgrey = {0.2, 0.2, 0.2, 1}; + RLVector4 grey = {0.4, 0.4, 0.4, 1}; + + UI(.flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow) { + UI(.padding={.left=20, .right=20, .top=5}, .color=darkgrey, .flags=UI_Flag_HeightGrow | UI_Flag_Vertical) { + UI() UI_Text(s("Somaesque"), .lineHeight=26); ui_Interaction(ui, soma); } - UI(ui, .flags=UI_Flag_Vertical | UI_Flag_HeightGrow | UI_Flag_WidthGrow) { - UI(ui, .padding=UI_PadUniform(5), .color={0.2, 0.2, 0.2, 1}, .flags=UI_Flag_WidthGrow) { - UI(ui, .width=65); - UI(ui, .flags=UI_Flag_WidthGrow); - UI(ui) { - RLVector4 color = {0.5, 0.5, 0.5, 1}; - UI(ui, .childGap=10) { - if (ui_Button(ui, UI_CreateRect(.width=30, .height=30, .color=color))) { + UI(.flags=UI_Flag_Vertical | UI_Flag_HeightGrow | UI_Flag_WidthGrow) { + UI(.padding=UI_PadUniform(10), .color=darkgrey, .flags=UI_Flag_WidthGrow) { + UI(.flags=UI_Flag_WidthGrow) { + UI(.childGap=10) { + if (ui_Button(ui, UI_CreateRect(.height=30, .padding={.left=10, .right=10}, .color=grey), UI_CreateText(s("Previous"), .lineHeight=20))) { advanceDisplayReverse(soma); } - if (ui_Button(ui, UI_CreateRect(.width=30, .height=30, .color=color))) { + if (ui_Button(ui, UI_CreateRect(.height=30, .padding={.left=10, .right=10}, .color=grey), UI_CreateText(s("Next"), .lineHeight=20))) { advanceDisplay(soma); } } } - UI(ui, .flags=UI_Flag_WidthGrow); - UI(ui, .width=65, .childGap=5) { + UI(.childGap=10) { if (ui_Button(ui, UI_CreateRect( .width=30, .height=30, - .color=soma->state.displayingSolutions ? (RLVector4){1,0,0,0.5} : COLOR_RED, - ))) { + .padding={.left=10, .right=10}, + .color=soma->state.displayingSolutions ? grey : (RLVector4){0.2, 0.2, 0.7, 1}, + ), UI_CreateText(s("Design"), .lineHeight=20))) { soma->state.displayingSolutions = false; } if (ui_Button(ui, UI_CreateRect( .width=30, .height=30, - .color=soma->state.displayingSolutions ? COLOR_GREEN : (RLVector4){0,1,0,0.5}, - ))) { + .padding={.left=10, .right=10}, + .color=soma->state.displayingSolutions ? (RLVector4){0.2, 0.2, 0.7, 1} : grey, + ), UI_CreateText(s("Solve"), .lineHeight=20))) { if (!soma->state.displayingSolutions) { tryScheduleSolve(soma); } else { @@ -424,7 +426,7 @@ static void DJUI_Soma(UI_Context *ui, Soma *soma) { } } } - UI(ui, .flags=UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_3DScene); + UI(.flags=UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_3DScene); } } } @@ -526,7 +528,8 @@ int mainGfx() { renderer.phongShader = &phongShader; renderer.cubeMesh = &cubeMesh; - UI_Context ui = ui_initContext(arena, &renderer); + Arena *uiArena = arenaAlloc(Megabytes(64)); + UI_Context ui = ui_initContext(uiArena, &renderer); Soma soma = { .window = { @@ -591,6 +594,13 @@ int mainGfx() { getSceneGraphNode(&mainScene, renderer.light)->translation = (RLVector3){4.0f, 6.0f, 24.0f}; + Font kodeMono = createFont(arena, s("./assets/fonts/KodeMono.ttf"), 96.0f); + renderer.activeFont = &kodeMono; + + Shader textShader = createShader(s("./assets/shaders/text.vertex.glsl"), s("./assets/shaders/text.fragment.glsl")); + renderer.textShader = &textShader; + + // Render loop real64 lastFrame = glfwGetTime(); real64 timeDelta = 1.0f / TARGET_FPS; real64 frameStart = lastFrame; diff --git a/src/render.c b/src/render.c index 40ea63a..ca4ddae 100644 --- a/src/render.c +++ b/src/render.c @@ -1,87 +1,144 @@ #include "render.h" #include "debug.h" -RenderObjects_Rectangle createRectangleObjects(Arena *arena, size_t count) { - RenderObjects_Rectangle result = {0}; +static RenderObjects_Char createCharObjects(Arena *arena, size_t count) { + RenderObjects_Char result = {0}; result.count = count; - result.p0 = PushFullList(arena, RLVec2List, count); - result.p1 = PushFullList(arena, RLVec2List, count); - result.color = PushFullList(arena, RLVec4List, count); - result.borderRadius = PushFullList(arena, FloatList, count); - result.borderThickness = PushFullList(arena, FloatList, count); - result.edgeSoftness = PushFullList(arena, FloatList, count); + result.begin.buf = PushFullList(arena, RLVec2List, count); + result.glyph.buf = PushFullList(arena, IntList, count); + result.lineHeight.buf = PushFullList(arena, FloatList, count); + result.color.buf = PushFullList(arena, RLVec4List, count); glGenVertexArrays(1, &result.vao); - glGenBuffers(1, &result.p0BufferId); - glGenBuffers(1, &result.p1BufferId); - glGenBuffers(1, &result.colorBufferId); - glGenBuffers(1, &result.borderRadiusBufferId); - glGenBuffers(1, &result.borderThicknessBufferId); - glGenBuffers(1, &result.edgeSoftnessBufferId); + glGenBuffers(1, &result.begin.bufId); + glGenBuffers(1, &result.glyph.bufId); + glGenBuffers(1, &result.lineHeight.bufId); + glGenBuffers(1, &result.color.bufId); glBindVertexArray(result.vao); - glBindBuffer(GL_ARRAY_BUFFER, result.p0BufferId); - glBufferData(GL_ARRAY_BUFFER, result.p0.length * sizeof(RLVec2List_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(RLVec2List_underlying), NULL); + int32 bufItemSize = sizeof(result.begin.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.begin.bufId); + glBufferData(GL_ARRAY_BUFFER, result.begin.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, bufItemSize, NULL); glVertexAttribDivisor(0, 1); glEnableVertexAttribArray(0); - glBindBuffer(GL_ARRAY_BUFFER, result.p1BufferId); - glBufferData(GL_ARRAY_BUFFER, result.p1.length * sizeof(RLVec2List_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(RLVec2List_underlying), NULL); + bufItemSize = sizeof(result.glyph.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.glyph.bufId); + glBufferData(GL_ARRAY_BUFFER, result.glyph.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribIPointer(1, 1, GL_INT, bufItemSize, NULL); glVertexAttribDivisor(1, 1); glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, result.colorBufferId); - glBufferData(GL_ARRAY_BUFFER, result.color.length * sizeof(RLVec4List_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(RLVec4List_underlying), NULL); + bufItemSize = sizeof(result.lineHeight.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.lineHeight.bufId); + glBufferData(GL_ARRAY_BUFFER, result.lineHeight.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, bufItemSize, NULL); glVertexAttribDivisor(2, 1); glEnableVertexAttribArray(2); - glBindBuffer(GL_ARRAY_BUFFER, result.borderRadiusBufferId); - glBufferData(GL_ARRAY_BUFFER, result.borderRadius.length * sizeof(FloatList_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(FloatList_underlying), NULL); + bufItemSize = sizeof(result.color.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.color.bufId); + glBufferData(GL_ARRAY_BUFFER, result.color.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, bufItemSize, NULL); glVertexAttribDivisor(3, 1); glEnableVertexAttribArray(3); - glBindBuffer(GL_ARRAY_BUFFER, result.borderThicknessBufferId); - glBufferData(GL_ARRAY_BUFFER, result.borderThickness.length * sizeof(FloatList_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, sizeof(FloatList_underlying), NULL); + return result; +} + +static RenderObjects_Rect createRectangleObjects(Arena *arena, size_t count) { + RenderObjects_Rect result = {0}; + result.count = count; + + result.p0.buf = PushFullList(arena, RLVec2List, count); + result.p1.buf = PushFullList(arena, RLVec2List, count); + result.color.buf = PushFullList(arena, RLVec4List, count); + result.borderRadius.buf = PushFullList(arena, FloatList, count); + result.borderThickness.buf = PushFullList(arena, FloatList, count); + result.edgeSoftness.buf = PushFullList(arena, FloatList, count); + + glGenVertexArrays(1, &result.vao); + + glGenBuffers(1, &result.p0.bufId); + glGenBuffers(1, &result.p1.bufId); + glGenBuffers(1, &result.color.bufId); + glGenBuffers(1, &result.borderRadius.bufId); + glGenBuffers(1, &result.borderThickness.bufId); + glGenBuffers(1, &result.edgeSoftness.bufId); + glGenBuffers(1, &result.edgeSoftness.bufId); + + glBindVertexArray(result.vao); + + int32 bufItemSize = sizeof(result.p0.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.p0.bufId); + glBufferData(GL_ARRAY_BUFFER, result.p0.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, bufItemSize, NULL); + glVertexAttribDivisor(0, 1); + glEnableVertexAttribArray(0); + + bufItemSize = sizeof(result.p1.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.p1.bufId); + glBufferData(GL_ARRAY_BUFFER, result.p1.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, bufItemSize, NULL); + glVertexAttribDivisor(1, 1); + glEnableVertexAttribArray(1); + + bufItemSize = sizeof(result.color.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.color.bufId); + glBufferData(GL_ARRAY_BUFFER, result.color.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, bufItemSize, NULL); + glVertexAttribDivisor(2, 1); + glEnableVertexAttribArray(2); + + bufItemSize = sizeof(result.borderRadius.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.borderRadius.bufId); + glBufferData(GL_ARRAY_BUFFER, result.borderRadius.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, bufItemSize, NULL); + glVertexAttribDivisor(3, 1); + glEnableVertexAttribArray(3); + + bufItemSize = sizeof(result.borderThickness.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.borderThickness.bufId); + glBufferData(GL_ARRAY_BUFFER, result.borderThickness.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, bufItemSize, NULL); glVertexAttribDivisor(4, 1); glEnableVertexAttribArray(4); - glBindBuffer(GL_ARRAY_BUFFER, result.edgeSoftnessBufferId); - glBufferData(GL_ARRAY_BUFFER, result.edgeSoftness.length * sizeof(FloatList_underlying), 0, GL_DYNAMIC_DRAW); - glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, sizeof(FloatList_underlying), NULL); + bufItemSize = sizeof(result.edgeSoftness.buf.data[0]); + glBindBuffer(GL_ARRAY_BUFFER, result.edgeSoftness.bufId); + glBufferData(GL_ARRAY_BUFFER, result.edgeSoftness.buf.length * bufItemSize, 0, GL_STREAM_DRAW); + glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, bufItemSize, NULL); glVertexAttribDivisor(5, 1); glEnableVertexAttribArray(5); return result; } -void updateRectangleObjectBuffers(Renderer *r) { +#define GL_UpdateBuffer(buffer) \ + glBindBuffer(GL_ARRAY_BUFFER, (buffer).bufId);\ + glBufferSubData(GL_ARRAY_BUFFER, 0, (buffer).buf.length * sizeof((buffer).underlying), (buffer).buf.data); + +static void updateCharObjectBuffers(Renderer *r) { + glBindVertexArray(r->chars.vao); + GL_UpdateBuffer(r->chars.begin); + GL_UpdateBuffer(r->chars.glyph); + GL_UpdateBuffer(r->chars.lineHeight); + GL_UpdateBuffer(r->chars.color); +} + + +static void updateRectangleObjectBuffers(Renderer *r) { glBindVertexArray(r->rects.vao); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.p0BufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.p0.length * sizeof(RLVec2List_underlying), r->rects.p0.data); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.p1BufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.p1.length * sizeof(RLVec2List_underlying), r->rects.p1.data); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.colorBufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.color.length * sizeof(RLVec4List_underlying), r->rects.color.data); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.borderRadiusBufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.borderRadius.length * sizeof(FloatList_underlying), r->rects.borderRadius.data); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.borderThicknessBufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.borderThickness.length * sizeof(FloatList_underlying), r->rects.borderThickness.data); - - glBindBuffer(GL_ARRAY_BUFFER, r->rects.edgeSoftnessBufferId); - glBufferSubData(GL_ARRAY_BUFFER, 0, r->rects.edgeSoftness.length * sizeof(FloatList_underlying), r->rects.edgeSoftness.data); + GL_UpdateBuffer(r->rects.p0); + GL_UpdateBuffer(r->rects.p1); + GL_UpdateBuffer(r->rects.color); + GL_UpdateBuffer(r->rects.borderRadius); + GL_UpdateBuffer(r->rects.borderThickness); + GL_UpdateBuffer(r->rects.edgeSoftness); } Renderer createRenderer(Arena *arena, Scene *scene, Camera *cam, int32 light) { @@ -90,16 +147,23 @@ Renderer createRenderer(Arena *arena, Scene *scene, Camera *cam, int32 light) { .light = light, .camera = cam, .rects = createRectangleObjects(arena, 1024), + .chars = createCharObjects(arena, 10000), }; } void renderBegin(Renderer *r) { - r->rects.p0.length = 0; - r->rects.p1.length = 0; - r->rects.color.length = 0; - r->rects.borderRadius.length = 0; - r->rects.borderThickness.length = 0; - r->rects.edgeSoftness.length = 0; + r->rects.p0.buf.length = 0; + r->rects.p1.buf.length = 0; + r->rects.color.buf.length = 0; + r->rects.borderRadius.buf.length = 0; + r->rects.borderThickness.buf.length = 0; + r->rects.edgeSoftness.buf.length = 0; + + r->chars.begin.buf.length = 0; + r->chars.glyph.buf.length = 0; + r->chars.lineHeight.buf.length = 0; + r->chars.color.buf.length = 0; + r->sceneWidth = 0; r->sceneHeight = 0; r->sceneX = 0; @@ -107,10 +171,11 @@ void renderBegin(Renderer *r) { } void renderEnd(Renderer *r) { - // 3D Entities + // 3D Scene glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glEnable(GL_DEPTH_TEST); + glViewport(r->sceneX, r->height - r->sceneY - r->sceneHeight, r->sceneWidth, r->sceneHeight); cameraSetAspect(r->camera, r->sceneWidth, r->sceneHeight); @@ -124,8 +189,6 @@ void renderEnd(Renderer *r) { glBindVertexArray(r->cubeMesh->vao); - // TODO(djledda): sort by mesh, texture, etc. - int model_uniform = getUniformLocation(r->phongShader, "model"); int solid_color_uniform = getUniformLocation(r->phongShader, "solid_color"); for (EachIn(r->scene->entities, i)) { @@ -141,29 +204,55 @@ void renderEnd(Renderer *r) { // 2D overlay glViewport(0, 0, r->width, r->height); - glUseProgram(r->solidShader->progId); - updateRectangleObjectBuffers(r); - glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Matrix ortho = MatrixOrtho(0.0, r->width, r->height, 0.0, -1.0, 1.0); - setUniformMat4fv(r->solidShader, "projection", &ortho); + // 1. Rects + updateRectangleObjectBuffers(r); + setUniformMat4fv(r->solidShader, "projection", &ortho); glBindVertexArray(r->rects.vao); - glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->rects.p0.length); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->rects.p0.buf.length); + + // 2. Text + updateCharObjectBuffers(r); + glUseProgram(r->textShader->progId); + setUniformMat4fv(r->textShader, "projection", &ortho); + glEnable(GL_TEXTURE_2D); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, r->activeFont->texId); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_BUFFER, r->activeFont->glyphTableTexId); + setUniform1i(r->textShader, "glyph_table", 1); + + glBindVertexArray(r->chars.vao); + glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->chars.begin.buf.length); + glDisable(GL_BLEND); } void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real32 height, RLVector4 color, real32 borderRadius, real32 borderThickness) { - ListAppend(r->rects.p0, ((RLVector2){ x, y })); - ListAppend(r->rects.p1, ((RLVector2){ x + width, y + height })); - ListAppend(r->rects.color, color); - ListAppend(r->rects.borderRadius, borderRadius); - ListAppend(r->rects.borderThickness, borderThickness); - ListAppend(r->rects.edgeSoftness, 0.15f); + ListAppend(r->rects.p0.buf, ((RLVector2){ x, y })); + ListAppend(r->rects.p1.buf, ((RLVector2){ x + width, y + height })); + ListAppend(r->rects.color.buf, color); + ListAppend(r->rects.borderRadius.buf, borderRadius); + ListAppend(r->rects.borderThickness.buf, borderThickness); + ListAppend(r->rects.edgeSoftness.buf, 0.15f); } +void rendererPlaceString(Renderer *r, string s, real32 x, real32 y, RLVector4 color, real32 lineHeight) { + real32 ratio = lineHeight / r->activeFont->lineHeight; + real32 charWidth = ratio * r->activeFont->charWidth; + for (int i = 0; i < s.length; i++) { + ListAppend(r->chars.begin.buf, ((RLVector2){ .x=x + i*charWidth, .y=y })); + ListAppend(r->chars.glyph.buf, s.str[i] - 32); + ListAppend(r->chars.lineHeight.buf, ratio); + ListAppend(r->chars.color.buf, (color)); + } +} diff --git a/src/render.h b/src/render.h index 955ab3e..572a66d 100644 --- a/src/render.h +++ b/src/render.h @@ -4,42 +4,74 @@ #include "gfx/Shader.h" #include "gfx/gfx.h" #include "world/world.h" -#include "lib/djstdlib/core.h" +#include "common.h" -DefineList(RLVector2, RLVec2); -DefineList(RLVector4, RLVec4); -DefineList(real32, Float); +typedef struct RenderObject_BufferInt32 RenderObject_BufferInt32; +struct RenderObject_BufferInt32 { + IntList buf; + uint32 bufId; + IntList_underlying underlying; +}; +typedef struct RenderObject_BufferFloat RenderObject_BufferFloat; +struct RenderObject_BufferFloat { + FloatList buf; + uint32 bufId; + FloatList_underlying underlying; +}; +typedef struct RenderObject_BufferVec2 RenderObject_BufferVec2; +struct RenderObject_BufferVec2 { + RLVec2List buf; + uint32 bufId; + RLVec2List_underlying underlying; +}; +typedef struct RenderObject_BufferVec4 RenderObject_BufferVec4; +struct RenderObject_BufferVec4 { + RLVec4List buf; + uint32 bufId; + RLVec4List_underlying underlying; +}; -typedef struct RenderObjects_Rectangle RenderObjects_Rectangle; -struct RenderObjects_Rectangle { + +typedef struct RenderObjects_Rect RenderObjects_Rect; +struct RenderObjects_Rect { uint32 vao; uint64 count; + RenderObject_BufferVec2 p0; + RenderObject_BufferVec2 p1; + RenderObject_BufferVec4 color; + RenderObject_BufferFloat borderRadius; + RenderObject_BufferFloat borderThickness; + RenderObject_BufferFloat edgeSoftness; +}; - RLVec2List p0; - uint32 p0BufferId; +typedef struct RenderObjects_Char RenderObjects_Char; +struct RenderObjects_Char { + uint32 vao; + uint64 count; + RenderObject_BufferVec2 begin; + RenderObject_BufferInt32 glyph; + RenderObject_BufferFloat lineHeight; + RenderObject_BufferVec4 color; +}; - RLVec2List p1; - uint32 p1BufferId; - - RLVec4List color; - uint32 colorBufferId; - - FloatList borderRadius; - uint32 borderRadiusBufferId; - - FloatList borderThickness; - uint32 borderThicknessBufferId; - - FloatList edgeSoftness; - uint32 edgeSoftnessBufferId; +typedef struct GlyphData GlyphData; +struct GlyphData { + real32 x0; + real32 y0; + real32 x1; + real32 y1; }; typedef struct Renderer Renderer; struct Renderer { - RenderObjects_Rectangle rects; + RenderObjects_Rect rects; + RenderObjects_Char chars; Shader *phongShader; Shader *solidShader; + Shader *textShader; + + Font *activeFont; int32 width; int32 height; @@ -60,7 +92,7 @@ Renderer createRenderer(Arena *arena, Scene *scene, Camera *cam, int32 light); void renderBegin(Renderer *r); void renderEnd(Renderer *r); void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real32 height, RLVector4 color, real32 borderRadius, real32 borderThickness); -void updateRectangleObjectBuffers(Renderer *r); +void rendererPlaceString(Renderer *r, string s, real32 x, real32 y, RLVector4 color, real32 fontSize); #define Render(r) DeferLoop(renderBegin((r)), renderEnd((r))) #endif diff --git a/src/ui.c b/src/ui.c index 950d815..f948b37 100644 --- a/src/ui.c +++ b/src/ui.c @@ -2,6 +2,9 @@ #include "GLFW/glfw3.h" #include "lib/djstdlib/core.h" #include +#include + +UI_Context *__UI_current_ctx__ = NULL; static bool glfwMouse(GLFWwindow *window, int mouseBtnCode) { switch (glfwGetMouseButton(window, mouseBtnCode)) { @@ -48,6 +51,7 @@ UI_Context ui_initContext(Arena *arena, Renderer *renderer) { UI_RectList list = PushListZero(arena, UI_RectList, Thousand(10)); ListAppend(list, ((UI_Rect){0})); // empty item return (UI_Context){ + .arena=arena, .rects=list, .prevRects=prevList, .hotNode = 0, @@ -58,30 +62,16 @@ UI_Context ui_initContext(Arena *arena, Renderer *renderer) { }; } -static void ui_playground(UI_Context *ui) { - UI(ui, - .width=ui->renderer->width, - .height=ui->renderer->height, - .color=COLOR_WHITE, - ) { - UI(ui, - .color=COLOR_GREEN, - .childGap=10, - .flags=UI_Flag_HeightGrow, - .padding=UI_PadUniform(10), - ) { - for (int i = 0; i < 4; i++) { - UI(ui, - .width=100, - .flags=UI_Flag_HeightGrow, - .color=COLOR_RED, - .borderRadius=10, - ); - } - } - - UI(ui, .color=COLOR_CYAN, .flags=UI_Flag_HeightGrow | UI_Flag_WidthGrow); +void ui_placeText(UI_Context *ui, UI_RectStr strData) { + UI_RectStr *strDataCopy = PushStruct(ui->arena, UI_RectStr); + *strDataCopy = strData; + if (strDataCopy->lineHeight == -1) { + strDataCopy->lineHeight = ui->renderer->activeFont->lineHeight; } + strDataCopy->s = PushString(ui->arena, strData.s.length); + memcpy(strDataCopy->s.str, strData.s.str, strData.s.length); + UI_Rect *currRect = &ui->rects.data[ui->currRect]; + currRect->stringData = strDataCopy; } void ui_sizingPass(UI_Context *ui, bool isXAxis, int32 rectHandle) { @@ -116,18 +106,22 @@ void ui_sizingPass(UI_Context *ui, bool isXAxis, int32 rectHandle) { while (childHandle) { child = &ui->rects.data[childHandle]; - if (isVertical && (child->flags & UI_Flag_WidthGrow) && isXAxis) { - child->resolvedWidth = rect->resolvedWidth - rect->padding.left - rect->padding.right; - } - if (!isVertical && (child->flags & UI_Flag_HeightGrow) && !isXAxis) { - child->resolvedHeight = rect->resolvedHeight - rect->padding.top - rect->padding.bottom; - } - - if (child->flags & UI_Flag_WidthGrow && !isVertical && isXAxis) { - child->resolvedWidth += childBreadthInc; - } - if (child->flags & UI_Flag_HeightGrow && isVertical && !isXAxis) { - child->resolvedHeight += childBreadthInc; + if (isXAxis) { + if (child->flags & UI_Flag_WidthGrow) { + if (isVertical) { + child->resolvedWidth = rect->resolvedWidth - rect->padding.left - rect->padding.right; + } else { + child->resolvedWidth += childBreadthInc; + } + } + } else { + if (child->flags & UI_Flag_HeightGrow) { + if (!isVertical) { + child->resolvedHeight = rect->resolvedHeight - rect->padding.top - rect->padding.bottom; + } else { + child->resolvedHeight += childBreadthInc; + } + } } ui_sizingPass(ui, isXAxis, childHandle); @@ -142,7 +136,9 @@ void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { bool isVertical = (rect->flags & UI_Flag_Vertical); - real32 coord = isXAxis ? rect->x + rect->xOffset + rect->padding.left : rect->y + rect->yOffset + rect->padding.top; + real32 coord = isXAxis + ? rect->x + rect->xOffset + rect->padding.left + : rect->y + rect->yOffset + rect->padding.top; int32 childHandle = rect->firstChild; UI_Rect *child; @@ -150,9 +146,13 @@ void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { child = &ui->rects.data[childHandle]; if (isXAxis) { - child->x = coord; + child->x = child->flags & UI_Flag_Center && isVertical + ? coord + (rect->resolvedWidth - rect->padding.left - rect->padding.right)/2 - child->resolvedWidth/2 + : coord; } else { - child->y = coord; + child->y = child->flags & UI_Flag_Center && !isVertical + ? coord + (rect->resolvedHeight - rect->padding.top - rect->padding.bottom)/2 - child->resolvedHeight/2 + : coord; } ui_calcLayout(ui, isXAxis, childHandle); @@ -167,6 +167,8 @@ void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { } void ui_begin(UI_Context *ui) { + __UI_current_ctx__ = ui; + arenaFreeFrom(ui->arena, 0); ClearList(ui->prevRects); ListAppendList(ui->prevRects, ui->rects); ui->cursorIsPointer = false; @@ -204,6 +206,17 @@ void ui_end(UI_Context *ui) { rect->borderRadius, rect->borderThickness ); + if (rect->stringData) { + UI_RectStr *str = rect->stringData; + rendererPlaceString( + ui->renderer, + str->s, + rect->x + str->xOffset, + rect->y + str->yOffset, + str->color, + str->lineHeight + ); + } } if (ui->scene3DHandle) { @@ -213,10 +226,12 @@ void ui_end(UI_Context *ui) { ui->renderer->sceneWidth = (int32)scene3DRect->resolvedWidth; ui->renderer->sceneHeight = (int32)scene3DRect->resolvedHeight; } + + __UI_current_ctx__ = NULL; } static bool pointInRect(real32 x, real32 y, UI_Rect rect) { - return x > rect.x && y > rect.y && x < (rect.x + rect.width) && y < (rect.y + rect.height); + return x > rect.x && y > rect.y && x < (rect.x + rect.resolvedWidth) && y < (rect.y + rect.resolvedHeight); } void ui_openElement(UI_Context *ui, UI_Rect rect) { @@ -252,18 +267,35 @@ void ui_closeElement(UI_Context *ui) { if (currRect->width != -1) { currRect->resolvedWidth = currRect->width; } - currRect->resolvedWidth += currRect->padding.left + currRect->padding.right; + if (currRect->minWidth == -1) { + if (currRect->stringData) { + real32 charWidth = currRect->stringData->lineHeight / ui->renderer->activeFont->lineHeight * ui->renderer->activeFont->charWidth; + currRect->minWidth = charWidth*currRect->stringData->s.length; + } else { + currRect->minWidth = 0; + } + } + currRect->resolvedWidth = currRect->padding.left + currRect->padding.right + (currRect->resolvedWidth < currRect->minWidth ? currRect->minWidth : currRect->resolvedWidth); + if (currRect->height != -1) { currRect->resolvedHeight = currRect->height; + } else if (currRect->minHeight == -1) { + if (currRect->stringData) { + currRect->minHeight = currRect->stringData->lineHeight; + } else { + currRect->minHeight = 0; + } } - currRect->resolvedHeight += currRect->padding.top + currRect->padding.bottom; + currRect->resolvedHeight = currRect->padding.top + currRect->padding.bottom + (currRect->resolvedHeight < currRect->minHeight ? currRect->minHeight : currRect->resolvedHeight); bool vertical = parentRect->flags & UI_Flag_Vertical; real32 currBreadth = vertical ? currRect->resolvedHeight : currRect->resolvedWidth; real32 currCrossBreadth = vertical ? currRect->resolvedWidth : currRect->resolvedHeight; real32 parentBreadth = vertical ? parentRect->height : parentRect->width; + real32 currParentBreadth = vertical ? parentRect->resolvedHeight : parentRect->resolvedWidth; real32 parentCrossBreadth = vertical ? parentRect->width : parentRect->height; + real32 currParentCrossBreadth = vertical ? parentRect->resolvedWidth : parentRect->resolvedHeight; real32 gap = parentRect->childGap; real32 breadthInc = (parentRect->firstChild == ui->currRect ? 0 : gap) + currBreadth; @@ -276,7 +308,7 @@ void ui_closeElement(UI_Context *ui) { } } - real32 newCrossBreadth = currCrossBreadth > parentCrossBreadth ? currCrossBreadth : parentCrossBreadth; + real32 newCrossBreadth = currCrossBreadth > currParentCrossBreadth ? currCrossBreadth : currParentCrossBreadth; if (parentCrossBreadth == -1) { if (vertical) { parentRect->resolvedWidth = newCrossBreadth; @@ -288,8 +320,8 @@ void ui_closeElement(UI_Context *ui) { ui->currRect = ui->rects.data[ui->currRect].parent; } -bool ui_Button(UI_Context *ui, UI_Rect rect) { - int32 id = UI_NextID(ui); +bool ui_Button(UI_Context *ui, UI_Rect rect, UI_RectStr textAttr) { + int32 id = UI_NextID(); bool clicked = false; if (pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id])) { @@ -302,8 +334,9 @@ bool ui_Button(UI_Context *ui, UI_Rect rect) { } rect.borderRadius = 5; - rect.borderThickness = 0; - UI_FromRect(ui, rect); + UI_FromRect(rect) { + ui_placeText(ui, textAttr); + } return clicked; } @@ -312,7 +345,7 @@ bool ui_Button(UI_Context *ui, UI_Rect rect) { * Returns whether the checkbox was clicked */ bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect) { - int32 id = UI_NextID(ui); + int32 id = UI_NextID(); bool clicked = false; if (pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id])) { ui->cursorIsPointer = true; @@ -326,12 +359,12 @@ bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect) { if (*value) { rect.borderRadius = 5; rect.borderThickness = 0; - UI_FromRect(ui, rect); + UI_FromRect(rect); } else { rect.borderRadius = 5; rect.borderThickness = 2; rect.color = COLOR_WHITE; - UI_FromRect(ui, rect); + UI_FromRect(rect); } return clicked; diff --git a/src/ui.h b/src/ui.h index 3032b9b..f836161 100644 --- a/src/ui.h +++ b/src/ui.h @@ -41,6 +41,7 @@ enum UI_Flag { UI_Flag_HeightGrow=1<<1, UI_Flag_Vertical=1<<2, // Default is horizontal UI_Flag_3DScene=1<<3, + UI_Flag_Center=1<<4, // .. UI_Flag_COUNT, }; @@ -53,9 +54,18 @@ struct UI_Padding { real32 left; }; +typedef struct UI_RectStr UI_RectStr; +struct UI_RectStr { + string s; + real32 xOffset; + real32 yOffset; + real32 lineHeight; + RLVector4 color; +}; + typedef struct UI_Rect UI_Rect; struct UI_Rect { - // UI_Rect_LayoutFlag + /** UI_Rect_LayoutFlag */ uint64 flags; int32 parent; @@ -87,11 +97,14 @@ struct UI_Rect { real32 y; real32 resolvedWidth; real32 resolvedHeight; + + UI_RectStr *stringData; }; DefineList(UI_Rect, UI_Rect); typedef struct UI_Context UI_Context; struct UI_Context { + Arena *arena; UI_RectList prevRects; UI_RectList rects; int32 hotNode; @@ -114,16 +127,21 @@ void ui_end(UI_Context *ui); UI_Context ui_initContext(Arena *arena, Renderer *renderer); void ui_resolve(); void ui_rect(UI_Context *ui, UI_Rect rect); +void ui_placeText(UI_Context *ui, UI_RectStr strData); void ui_openElement(UI_Context *ui, UI_Rect rect); void ui_closeElement(UI_Context *ui); bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect); -#define UI(ui, ...) DeferLoop(ui_openElement((ui), UI_CreateRect(__VA_ARGS__)), ui_closeElement((ui))) -#define UI_FromRect(ui, rect) DeferLoop(ui_openElement((ui), (rect)), ui_closeElement((ui))) -#define UI_CreateRect(...) ((UI_Rect){.width=-1, .height=-1, __VA_ARGS__}) +extern UI_Context *__UI_current_ctx__; + +#define UI(...) DeferLoop(ui_openElement((__UI_current_ctx__), UI_CreateRect(__VA_ARGS__)), ui_closeElement((__UI_current_ctx__))) +#define UI_CreateRect(...) ((UI_Rect){.minHeight=-1, .minWidth=-1, .width=-1, .height=-1, __VA_ARGS__}) +#define UI_FromRect(rect) DeferLoop(ui_openElement((__UI_current_ctx__), (rect)), ui_closeElement((__UI_current_ctx__))) #define UI_Pass(ui) DeferLoop(ui_begin((ui)), ui_end((ui))) #define UI_PadUniform(padding) ((UI_Padding){ (padding), (padding), (padding), (padding) }) -#define UI_NextID(ui) ((ui)->rects.length) +#define UI_NextID() ((__UI_current_ctx__)->rects.length) +#define UI_Text(str, ...) (ui_placeText((__UI_current_ctx__), ((UI_RectStr){ .xOffset=0, .yOffset=0, .lineHeight=-1, .s=(str), __VA_ARGS__ }))) +#define UI_CreateText(str, ...) ((UI_RectStr){ .xOffset=0, .yOffset=0, .lineHeight=-1, .s=(str), __VA_ARGS__ }) #endif