diff --git a/.clangd b/.clangd index 75a5926..5729e29 100644 --- a/.clangd +++ b/.clangd @@ -2,6 +2,7 @@ CompileFlags: Add: - -std=c99 - -xc + - -Wno-initializer-overrides # LINUX FLAGS - -DOS_LINUX - -DCOMPOSITOR_WAYLAND diff --git a/assets/shaders/2d-solid.fragment.glsl b/assets/shaders/2d-solid.fragment.glsl index e350f85..9860582 100644 --- a/assets/shaders/2d-solid.fragment.glsl +++ b/assets/shaders/2d-solid.fragment.glsl @@ -30,9 +30,9 @@ void main() { float interior_corner_radius = frag_border_radius * interior_radius_reduce_f * interior_radius_reduce_f; float inside_d = roundedRectSDF( - frag_dest_position, - frag_dest_center, - interior_half_size - softness_padding, + frag_dest_position, + frag_dest_center, + interior_half_size - softness_padding, interior_corner_radius); @@ -41,15 +41,15 @@ void main() { } float dist = roundedRectSDF( - frag_dest_position, - frag_dest_center, - frag_dest_half_size - softness_padding, + frag_dest_position, + frag_dest_center, + frag_dest_half_size - softness_padding, frag_border_radius); // For texturing later float sample = 1; - float sdf_factor = 1.0f - smoothstep(0, 2*frag_softness, dist); + float sdf_factor = 1 - smoothstep(0, 2*frag_softness, dist); pixel_color = frag_color * sample * sdf_factor * border_factor; }; diff --git a/build b/build index b95fd09..4fbd9f8 100755 --- a/build +++ b/build @@ -1,7 +1,7 @@ #!/bin/bash LIB_INCLUDE="-lglfw -lGL -lm" -COMMON_FLAGS="-DOS_LINUX=1 -xc -std=c99" +COMMON_FLAGS="-DOS_LINUX=1 -DCOMPOSITOR_WAYLAND=1 -xc -std=c99 -Wno-initializer-overrides" echo [Building target] if [ $DEBUG ]; then diff --git a/src/debug.c b/src/debug.c index 17db16e..ab14bd6 100644 --- a/src/debug.c +++ b/src/debug.c @@ -2,8 +2,7 @@ #include "lib/djstdlib/core.h" #include "debug.h" -void printRLVec3(RLVector3* vector) { - RLVector3 vec = *vector; +void printRLVec3(RLVector3 vec) { print( "┌ ┐\n" "│%7.2f%, %7.2f, %7.2f │\n" @@ -11,8 +10,7 @@ void printRLVec3(RLVector3* vector) { vec.x, vec.y, vec.z); } -void printMatrix(Matrix* matrix) { - Matrix mat = *matrix; +void printMatrix(Matrix mat) { print( "┌ ┐\n" "│%7.2f%, %7.2f, %7.2f, %7.2f │\n" diff --git a/src/debug.h b/src/debug.h index 2b97be5..08bcb7c 100644 --- a/src/debug.h +++ b/src/debug.h @@ -3,7 +3,7 @@ #include "lib/raymath.h" -void printRLVec3(RLVector3* vector); -void printMatrix(Matrix* matrix); +void printRLVec3(RLVector3 vector); +void printMatrix(Matrix matrix); #endif diff --git a/src/main.c b/src/main.c index 0114b9c..ce7041d 100644 --- a/src/main.c +++ b/src/main.c @@ -1,3 +1,4 @@ +#include "lib/djstdlib/core.h" #define _POSIX_C_SOURCE 199309L #include "time.h" @@ -65,6 +66,7 @@ struct SomaState { bool displayingSolutions; RLVector3 rotAxisX; RLVector3 rotAxisY; + UI_Rect *threedeePaneRect; }; typedef struct Soma Soma; @@ -84,6 +86,7 @@ struct Soma { SomaState prevState; SomaState state; + UI_Context *ui; PolycubeInputList polycubeInput; HandleList polycubes; SomaSolutionList solutions; @@ -101,7 +104,10 @@ void *executeSolve(void *ctx) { return NULL; } -void scheduleSolve(Soma *soma) { +void tryScheduleSolve(Soma *soma) { + if (soma->solveTaskCtx.taskStatus != SolveTaskStatus_Ready) { + return; + } VoxelSpaceReprList mappedInputs = PushList(soma->solveTaskCtx.arena, VoxelSpaceReprList, soma->polycubeInput.length); for (EachEl(soma->polycubeInput, PolycubeInput, polycubeInput)) { ListAppend(mappedInputs, polycubeInput->repr.space); @@ -130,7 +136,9 @@ RLVector3 centreFromPolycube(Scene *scene, uint32 p) { } void framebufferSizeCallback(GLFWwindow *window, int width, int height) { - glViewport(0, 0, width, height); + Soma *soma = (Soma *)glfwGetWindowUserPointer(window); + soma->renderer->width = width; + soma->renderer->height = height; } GLFWwindow *initWindowAndGL(uint32 windowWidth, uint32 windowHeight) { @@ -168,7 +176,7 @@ GLFWwindow *initWindowAndGL(uint32 windowWidth, uint32 windowHeight) { } glViewport(0, 0, windowWidth, windowHeight); - glfwSwapInterval(1); + glfwSwapInterval(0); glfwSetFramebufferSizeCallback(window, framebufferSizeCallback); glfwSetInputMode(window, GLFW_CURSOR | GLFW_RAW_MOUSE_MOTION, GLFW_CURSOR_NORMAL); glfwSetWindowSize(window, windowWidth, windowHeight); @@ -183,9 +191,47 @@ Texture wallTex = {0}; Shader solidColorShader; Shader phongShader; -void processInput(Soma *soma, UI_Context *ui) { - Input *input = ui->input; - Input *prevInput = ui->prevInput; +static void advanceDisplayReverse(Soma *soma) { + if (soma->state.displayingSolutions) { + if (soma->state.displayedSolution == 0) { + soma->state.displayedSolution = soma->solutions.length - 1; + } else { + soma->state.displayedSolution -= 1; + } + } else { + if (soma->state.displayedPolycube == 0) { + soma->state.displayedPolycube = soma->polycubeInput.length - 1; + } else { + soma->state.displayedPolycube -= 1; + } + } +} + +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; + } + } else { + if (soma->state.displayedPolycube == soma->polycubeInput.length - 1) { + soma->state.displayedPolycube = 0; + } else { + soma->state.displayedPolycube += 1; + } + } +} + +static void onMouseScroll(GLFWwindow *window, real64 deltaX, real64 deltaY) { + Soma *soma = (Soma *)glfwGetWindowUserPointer(window); + soma->ui->input->mouse.scroll.dX += deltaX; + soma->ui->input->mouse.scroll.dY += deltaY; +} + +void processInput(Soma *soma) { + Input *input = soma->ui->input; + Input *prevInput = soma->ui->prevInput; if (input->keyboard.escape) { glfwSetWindowShouldClose(soma->window.handle, true); @@ -203,31 +249,19 @@ void processInput(Soma *soma, UI_Context *ui) { node->translation.z += 1.0 * input->keyboard.z * shiftMultiplier; if (input->keyboard.space && !prevInput->keyboard.space) { - if (soma->state.displayingSolutions) { - if (soma->state.displayedSolution == soma->solutions.length - 1) { - soma->state.displayedSolution = 0; - } else { - soma->state.displayedSolution += 1; - } - } else { - if (soma->state.displayedPolycube == soma->polycubeInput.length - 1) { - soma->state.displayedPolycube = 0; - } else { - soma->state.displayedPolycube += 1; - } - } + advanceDisplay(soma); } if (input->keyboard.enter && !prevInput->keyboard.enter) { if (soma->state.displayingSolutions) { soma->state.displayingSolutions = false; soma->state.displayedSolution = -1; - } else if (soma->solveTaskCtx.taskStatus == SolveTaskStatus_Ready) { - scheduleSolve(soma); + } else { + tryScheduleSolve(soma); } } - if (input->mouse.btnLeft && ui->hotNode == 0) { + if (input->mouse.btnLeft && soma->ui->hotNode == 0) { uint32 currentObject = soma->state.displayingSolutions ? soma->solutionNode : soma->polycubes.data[soma->state.displayedPolycube]; @@ -243,6 +277,29 @@ void processInput(Soma *soma, UI_Context *ui) { objectGraphNode->rotation = QuaternionMultiply(QuaternionFromAxisAngle(soma->state.rotAxisX, -(real32)deltaY), objectGraphNode->rotation); } } + + if (soma->state.displayingSolutions) { + real64 scrollDelta = soma->ui->prevInput->mouse.scroll.dY; + if (scrollDelta > 0.001 || scrollDelta < -0.001) { + SceneGraphNode *rootNode = getSceneGraphNode(soma->scene, soma->solutionNode); + int32 nextChildHandle = rootNode->firstChild; + while (nextChildHandle) { + SceneGraphNode *nextChild = getSceneGraphNode(soma->scene, nextChildHandle); + RLVector3 centre = {0}; + int32 subsegmentHandle = nextChild->firstChild; + int32 subsegmentCount = 0; + while (subsegmentHandle) { + subsegmentCount++; + SceneGraphNode *subSegment = getSceneGraphNode(soma->scene, subsegmentHandle); + centre = Vector3Add(centre, subSegment->translation); + subsegmentHandle = subSegment->nextSibling; + } + RLVector3 flyDirection = Vector3Normalize(Vector3Scale(Vector3Transform(centre, nextChild->local), -1.f/subsegmentCount)); + nextChild->translation = Vector3Add(nextChild->translation, Vector3Scale(flyDirection, scrollDelta * 0.1)); + nextChildHandle = nextChild->nextSibling; + } + } + } } uint32 createPolycubeFromRepr(Scene *s, VoxelSpace *repr, RLVector4 color) { @@ -273,67 +330,101 @@ uint32 createPolycubeFromRepr(Scene *s, VoxelSpace *repr, RLVector4 color) { return mainGraphNode; } -static void somaUIPass(Soma *soma, UI_Context *ui) { +static void ui_Interaction(UI_Context *ui, Soma *soma) { real32 boxSize = 30; real32 padding = 20; - real32 paddingBetween = 5; - real32 currY = padding; + real32 childGap = 5; - if (soma->state.displayingSolutions) { - if (soma->solutions.length > 0) { + 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 (EachIn(*currentSolution, i)) { - uint64 spaceRepr = currentSolution->data[i]; - for (int x = 0; x < soma->puzzleDims[0]; x++) { - for (int y = 0; y < soma->puzzleDims[1]; y++) { - real32 currX = padding; - for (int z = 0; z < soma->puzzleDims[2]; z++) { + 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) { - rendererPlaceRectangle(ui->renderer, - currX, currY, - boxSize, boxSize, - soma->polycubeInput.data[i].color, - 5, 0); - } - currX += paddingBetween + boxSize; + if (cellActive) UI(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); } - currY += paddingBetween + boxSize; } - currY += padding; } - currY = padding; } } - } else { - PolycubeInput *currentPolycube = &soma->polycubeInput.data[soma->state.displayedPolycube]; - for (int x = 0; x < currentPolycube->repr.dim_x; x++) { - for (int y = 0; y < currentPolycube->repr.dim_y; y++) { - real32 currX = padding; - for (int z = 0; z < currentPolycube->repr.dim_z; z++) { - bool cellActive = filledAt(¤tPolycube->repr, x, y, z); - UI_Rect rect = { - .x = currX, - .y = currY, - .width = boxSize, - .height = boxSize, - .borderRadius = 2, - .color = currentPolycube->color, - }; - if (ui_checkboxRect(ui, &cellActive, rect)) { - soma->state.polycubeDirty = true; - spaceSet(¤tPolycube->repr, cellActive, x, y, z); + } +} + +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) { + 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))) { + advanceDisplayReverse(soma); + } + if (ui_Button(ui, UI_CreateRect(.width=30, .height=30, .color=color))) { + advanceDisplay(soma); + } + } + } + UI(ui, .flags=UI_Flag_WidthGrow); + UI(ui, .width=65, .childGap=5) { + if (ui_Button(ui, UI_CreateRect( + .width=30, + .height=30, + .color=soma->state.displayingSolutions ? (RLVector4){1,0,0,0.5} : COLOR_RED, + ))) { + 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}, + ))) { + if (!soma->state.displayingSolutions) { + tryScheduleSolve(soma); + } else { + soma->state.displayingSolutions = false; + } } - currX += paddingBetween + boxSize; } - currY += paddingBetween + boxSize; } - currY += padding; + UI(ui, .flags=UI_Flag_WidthGrow | UI_Flag_HeightGrow | UI_Flag_3DScene); } } } @@ -399,8 +490,6 @@ static void updatePolycubeDisplay(Soma *soma) { static void updateWindow(Soma *soma) { glfwGetWindowSize(soma->window.handle, &soma->window.width, &soma->window.height); - glViewport(0, 0, soma->window.width, soma->window.height); - cameraSetAspect(soma->renderer->camera, soma->window.width, soma->window.height); soma->renderer->width = soma->window.width; soma->renderer->height = soma->window.height; } @@ -450,6 +539,7 @@ int mainGfx() { }, }, .scene = &mainScene, + .ui = &ui, .renderer = &renderer, .polycubeInput = PushListZero(arena, PolycubeInputList, MAX_POLYCUBE_INPUT), .polycubes = PushListZero(arena, HandleList, MAX_POLYCUBE_INPUT), @@ -462,6 +552,7 @@ int mainGfx() { .taskStatus = SolveTaskStatus_Ready, }, .state = { + .threedeePaneRect=NULL, .displayedPolycube = 0, .displayingSolutions = false, .displayedSolution = -1, @@ -469,6 +560,9 @@ int mainGfx() { }, }; + glfwSetWindowUserPointer(windowHandle, &soma); + glfwSetScrollCallback(windowHandle, onMouseScroll); + VoxelSpaceReprList stdSoma = AsList(VoxelSpaceReprList, STD_SOMA); for (EachIn(stdSoma, i)) { VoxelSpace voxelSpace = { @@ -514,7 +608,7 @@ int mainGfx() { updateWindow(&soma); - processInput(&soma, &ui); + processInput(&soma); if (soma.solveTaskCtx.taskStatus == SolveTaskStatus_Complete) { soma.solutions = PushList(solutionsArena, SomaSolutionList, soma.solveTaskCtx.solutions.length); @@ -527,24 +621,22 @@ int mainGfx() { arenaFreeFrom(soma.solveTaskCtx.arena, 0); } + Render(&renderer) { + UI_Pass(&ui) { + DJUI_Soma(&ui, &soma); + } + + if (ui.cursorIsPointer) { + glfwSetCursor(soma.window.handle, soma.window.cursors.pointer); + } else { + glfwSetCursor(soma.window.handle, soma.window.cursors.arrow); + } + } + updatePolycubeDisplay(&soma); recalcScene(soma.scene); - renderBegin(&renderer); - - ui_begin(&ui); - somaUIPass(&soma, &ui); - ui_end(&ui); - - if (ui.cursorIsPointer) { - glfwSetCursor(soma.window.handle, soma.window.cursors.pointer); - } else { - glfwSetCursor(soma.window.handle, soma.window.cursors.arrow); - } - - renderEnd(&renderer); - glfwSwapBuffers(soma.window.handle); real64 frameEnd = glfwGetTime(); diff --git a/src/render.c b/src/render.c index 3619545..40ea63a 100644 --- a/src/render.c +++ b/src/render.c @@ -100,12 +100,19 @@ void renderBegin(Renderer *r) { r->rects.borderRadius.length = 0; r->rects.borderThickness.length = 0; r->rects.edgeSoftness.length = 0; + r->sceneWidth = 0; + r->sceneHeight = 0; + r->sceneX = 0; + r->sceneY = 0; } void renderEnd(Renderer *r) { // 3D Entities 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); glUseProgram(r->phongShader->progId); setUniformMat4fv(r->phongShader, "projection", &r->camera->proj); @@ -133,15 +140,22 @@ 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); glBindVertexArray(r->rects.vao); glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, r->rects.p0.length); + glDisable(GL_BLEND); } void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real32 height, RLVector4 color, real32 borderRadius, real32 borderThickness) { @@ -150,6 +164,6 @@ void rendererPlaceRectangle(Renderer *r, real32 x, real32 y, real32 width, real3 ListAppend(r->rects.color, color); ListAppend(r->rects.borderRadius, borderRadius); ListAppend(r->rects.borderThickness, borderThickness); - ListAppend(r->rects.edgeSoftness, 0.0f); + ListAppend(r->rects.edgeSoftness, 0.15f); } diff --git a/src/render.h b/src/render.h index 56ffa5a..955ab3e 100644 --- a/src/render.h +++ b/src/render.h @@ -4,6 +4,7 @@ #include "gfx/Shader.h" #include "gfx/gfx.h" #include "world/world.h" +#include "lib/djstdlib/core.h" DefineList(RLVector2, RLVec2); DefineList(RLVector4, RLVec4); @@ -43,6 +44,11 @@ struct Renderer { int32 width; int32 height; + int32 sceneWidth; + int32 sceneHeight; + int32 sceneX; + int32 sceneY; + Scene *scene; Camera *camera; /** SceneGraphNode handle */ @@ -55,5 +61,6 @@ 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); +#define Render(r) DeferLoop(renderBegin((r)), renderEnd((r))) #endif diff --git a/src/ui.c b/src/ui.c index b4fb3aa..950d815 100644 --- a/src/ui.c +++ b/src/ui.c @@ -1,5 +1,7 @@ #include "ui.h" #include "GLFW/glfw3.h" +#include "lib/djstdlib/core.h" +#include static bool glfwMouse(GLFWwindow *window, int mouseBtnCode) { switch (glfwGetMouseButton(window, mouseBtnCode)) { @@ -42,12 +44,13 @@ Input getCurrentInput(GLFWwindow *window) { } UI_Context ui_initContext(Arena *arena, Renderer *renderer) { + UI_RectList prevList = PushListZero(arena, UI_RectList, Thousand(10)); UI_RectList list = PushListZero(arena, UI_RectList, Thousand(10)); ListAppend(list, ((UI_Rect){0})); // empty item return (UI_Context){ .rects=list, + .prevRects=prevList, .hotNode = 0, - .nextId = 1, .cursorIsPointer = false, .input = NULL, .prevInput = NULL, @@ -55,14 +58,161 @@ 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_sizingPass(UI_Context *ui, bool isXAxis, int32 rectHandle) { + UI_Rect *rect = &ui->rects.data[rectHandle]; + if (!rect->firstChild) return; + + bool isVertical = rect->flags & UI_Flag_Vertical; + + real32 remainingSpace = (isXAxis + ? rect->resolvedWidth - rect->padding.left - rect->padding.right + : rect->resolvedHeight - rect->padding.top - rect->padding.bottom) + rect->childGap; + int32 growableChildrenCount = 0; + + UI_Rect *child; + + int32 childHandle = rect->firstChild; + if (isVertical != isXAxis) { + while (childHandle) { + child = &ui->rects.data[childHandle]; + if (child->flags & UI_Flag_HeightGrow && !isXAxis || child->flags & UI_Flag_WidthGrow && isXAxis) { + growableChildrenCount++; + } + real32 childBreadth = isXAxis ? child->resolvedWidth : child->resolvedHeight; + remainingSpace -= childBreadth + rect->childGap; + childHandle = child->nextSibling; + } + } + + real32 childBreadthInc = growableChildrenCount > 0 ? remainingSpace / (real32)growableChildrenCount : 0; + + childHandle = rect->firstChild; + 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; + } + + ui_sizingPass(ui, isXAxis, childHandle); + + childHandle = child->nextSibling; + } +} + +void ui_calcLayout(UI_Context *ui, bool isXAxis, int32 rectHandle) { + UI_Rect *rect = &ui->rects.data[rectHandle]; + if (!rect->firstChild) return; + + bool isVertical = (rect->flags & UI_Flag_Vertical); + + real32 coord = isXAxis ? rect->x + rect->xOffset + rect->padding.left : rect->y + rect->yOffset + rect->padding.top; + + int32 childHandle = rect->firstChild; + UI_Rect *child; + while (childHandle) { + child = &ui->rects.data[childHandle]; + + if (isXAxis) { + child->x = coord; + } else { + child->y = coord; + } + + ui_calcLayout(ui, isXAxis, childHandle); + + if (!isVertical == isXAxis) { + coord += isXAxis ? child->resolvedWidth : child->resolvedHeight; + coord += rect->childGap; + } + + childHandle = child->nextSibling; + } +} + void ui_begin(UI_Context *ui) { + ClearList(ui->prevRects); + ListAppendList(ui->prevRects, ui->rects); ui->cursorIsPointer = false; - ui->nextId = 1; ui->prevHotNode = ui->hotNode; ui->hotNode = 0; + ui->scene3DHandle = 0; + ui->rootRect = 0; + ui->currRect = 0; + ClearList(ui->rects); + ListAppend(ui->rects, (UI_Rect){0}); + + ui_openElement(ui, (UI_Rect){ .width=ui->renderer->width, .height=ui->renderer->height, .color=(RLVector4){0,0,0,0}}); } void ui_end(UI_Context *ui) { + ui_closeElement(ui); + + // 1. Calculate layout + ui_sizingPass(ui, true, ui->rootRect); + ui_sizingPass(ui, false, ui->rootRect); + + ui_calcLayout(ui, true, ui->rootRect); + ui_calcLayout(ui, false, ui->rootRect); + + // 2. Create render commands: + + for (EachEl(ui->rects, UI_Rect, rect)) { + rendererPlaceRectangle( + ui->renderer, + rect->x, + rect->y, + rect->resolvedWidth, + rect->resolvedHeight, + rect->color, + rect->borderRadius, + rect->borderThickness + ); + } + + if (ui->scene3DHandle) { + UI_Rect *scene3DRect = &ui->rects.data[ui->scene3DHandle]; + ui->renderer->sceneX = (int32)scene3DRect->x; + ui->renderer->sceneY = (int32)scene3DRect->y; + ui->renderer->sceneWidth = (int32)scene3DRect->resolvedWidth; + ui->renderer->sceneHeight = (int32)scene3DRect->resolvedHeight; + } } static bool pointInRect(real32 x, real32 y, UI_Rect rect) { @@ -70,31 +220,101 @@ static bool pointInRect(real32 x, real32 y, UI_Rect rect) { } void ui_openElement(UI_Context *ui, UI_Rect rect) { - ListAppend(ui->rects, rect); - int32 nextHandle = ui->rects.length - 1; - - UI_Rect *nextRect = &ui->rects.data[nextHandle]; + ListAppend(ui->rects, (UI_Rect){0}); + int32 newHandle = ui->rects.length - 1; + UI_Rect *newRect = &ui->rects.data[newHandle]; UI_Rect *currRect = &ui->rects.data[ui->currRect]; - nextRect->parent = ui->currRect; - nextRect->nextSibling = currRect->firstChild; - currRect->firstChild = nextHandle; + *newRect = rect; + newRect->parent = ui->currRect; + if (currRect->lastChild) { + ui->rects.data[currRect->lastChild].nextSibling = newHandle; + } else { + currRect->firstChild = newHandle; + } + currRect->lastChild = newHandle; - ui->currRect = nextHandle; + if (!ui->rootRect) { + ui->rootRect = newHandle; + } + + ui->currRect = newHandle; + + if (newRect->flags & UI_Flag_3DScene) { + ui->scene3DHandle = newHandle; + } } void ui_closeElement(UI_Context *ui) { + UI_Rect *currRect = &ui->rects.data[ui->currRect]; + UI_Rect *parentRect = &ui->rects.data[currRect->parent]; + + if (currRect->width != -1) { + currRect->resolvedWidth = currRect->width; + } + currRect->resolvedWidth += currRect->padding.left + currRect->padding.right; + if (currRect->height != -1) { + currRect->resolvedHeight = currRect->height; + } + currRect->resolvedHeight += currRect->padding.top + currRect->padding.bottom; + + 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 parentCrossBreadth = vertical ? parentRect->width : parentRect->height; + real32 gap = parentRect->childGap; + + real32 breadthInc = (parentRect->firstChild == ui->currRect ? 0 : gap) + currBreadth; + + if (parentBreadth == -1) { + if (vertical) { + parentRect->resolvedHeight += breadthInc; + } else { + parentRect->resolvedWidth += breadthInc; + } + } + + real32 newCrossBreadth = currCrossBreadth > parentCrossBreadth ? currCrossBreadth : parentCrossBreadth; + if (parentCrossBreadth == -1) { + if (vertical) { + parentRect->resolvedWidth = newCrossBreadth; + } else { + parentRect->resolvedHeight = newCrossBreadth; + } + } + ui->currRect = ui->rects.data[ui->currRect].parent; } +bool ui_Button(UI_Context *ui, UI_Rect rect) { + int32 id = UI_NextID(ui); + bool clicked = false; + + if (pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id])) { + ui->cursorIsPointer = true; + if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { + clicked = true; + } else if (ui->input->mouse.btnLeft && (!ui->prevInput->mouse.btnLeft || ui->prevHotNode == id)) { + ui->hotNode = id; + } + } + + rect.borderRadius = 5; + rect.borderThickness = 0; + UI_FromRect(ui, rect); + + return clicked; +} /** * Returns whether the checkbox was clicked */ -bool ui_checkboxRect(UI_Context *ui, bool *value, UI_Rect rect) { - uint32 id = ui->nextId++; +bool ui_CheckboxRect(UI_Context *ui, bool *value, UI_Rect rect) { + int32 id = UI_NextID(ui); bool clicked = false; - if (pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, rect)) { + if (pointInRect(ui->input->mouse.point.x, ui->input->mouse.point.y, ui->prevRects.data[id])) { ui->cursorIsPointer = true; if (ui->prevHotNode == id && !ui->input->mouse.btnLeft) { *value = !*value; @@ -106,12 +326,12 @@ bool ui_checkboxRect(UI_Context *ui, bool *value, UI_Rect rect) { if (*value) { rect.borderRadius = 5; rect.borderThickness = 0; - DJUI(ui, rect); + UI_FromRect(ui, rect); } else { rect.borderRadius = 5; rect.borderThickness = 2; rect.color = COLOR_WHITE; - DJUI(ui, rect); + UI_FromRect(ui, rect); } return clicked; diff --git a/src/ui.h b/src/ui.h index 2ead88a..3032b9b 100644 --- a/src/ui.h +++ b/src/ui.h @@ -26,26 +26,41 @@ struct Input { }; RLVector2 point; }; + struct { + real64 dX; + real64 dY; + } scroll; bool btnLeft; bool btnRight; bool btnMiddle; } mouse; }; -enum UI_Rect_LayoutFlag { - UI_Rect_LayoutFlag_Width_Grow=1<<0, // Default is fixed - UI_Rect_LayoutFlag_Height_Grow=1<<1, - UI_Rect_LayoutFlag_LayoutDirection_Vertical=1<<2, // Default is horizontal +enum UI_Flag { + UI_Flag_WidthGrow=1<<0, // Default is fixed + UI_Flag_HeightGrow=1<<1, + UI_Flag_Vertical=1<<2, // Default is horizontal + UI_Flag_3DScene=1<<3, // .. - UI_Rect_LayoutFlag_COUNT, + UI_Flag_COUNT, +}; + +typedef struct UI_Padding UI_Padding; +struct UI_Padding { + real32 top; + real32 right; + real32 bottom; + real32 left; }; typedef struct UI_Rect UI_Rect; struct UI_Rect { - uint64 layoutFlags; + // UI_Rect_LayoutFlag + uint64 flags; int32 parent; int32 firstChild; + int32 lastChild; int32 nextSibling; real32 xOffset; @@ -65,6 +80,7 @@ struct UI_Rect { RLVector4 color; RLVector4 borderColor; + UI_Padding padding; real32 childGap; real32 x; @@ -76,9 +92,10 @@ DefineList(UI_Rect, UI_Rect); typedef struct UI_Context UI_Context; struct UI_Context { + UI_RectList prevRects; UI_RectList rects; - int32 nextId; int32 hotNode; + int32 scene3DHandle; int32 prevHotNode; Renderer *renderer; @@ -86,19 +103,27 @@ struct UI_Context { Input *input; bool cursorIsPointer; + int32 rootRect; int32 currRect; }; +Input getCurrentInput(GLFWwindow *window); + void ui_begin(UI_Context *ui); void ui_end(UI_Context *ui); UI_Context ui_initContext(Arena *arena, Renderer *renderer); -Input getCurrentInput(GLFWwindow *window); void ui_resolve(); void ui_rect(UI_Context *ui, UI_Rect rect); -bool ui_checkboxRect(UI_Context *ui, bool *value, UI_Rect rect); void ui_openElement(UI_Context *ui, UI_Rect rect); void ui_closeElement(UI_Context *ui); -#define DJUI(ui, rect) DeferLoop(ui_openElement((ui), (rect)), ui_closeElement((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__}) +#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) #endif