From cc551a07e7a9204e31198fa6ea4317e687222132 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Sun, 23 Nov 2025 12:06:20 -0800 Subject: [PATCH 01/17] feat: draw titlebar from vertex buffer --- src/chess_board.c | 1 + src/shaders/titlebar.frag.glsl | 23 +-- src/shaders/titlebar.vert.glsl | 109 ++----------- src/textures/titlebar.png | Bin 1115 -> 1279 bytes src/textures/titlebar.svg | 24 ++- src/titlebar.c | 269 +++++++++++++++++++++++++++++---- xcode/modeler/ModelerApp.swift | 2 +- 7 files changed, 279 insertions(+), 149 deletions(-) diff --git a/src/chess_board.c b/src/chess_board.c index 5b37cfb..28f67df 100644 --- a/src/chess_board.c +++ b/src/chess_board.c @@ -5,6 +5,7 @@ #include "lodepng.h" #include "chess_board.h" +#include "buffer.h" #include "descriptor.h" #include "image.h" #include "image_view.h" diff --git a/src/shaders/titlebar.frag.glsl b/src/shaders/titlebar.frag.glsl index 6982561..7769d60 100644 --- a/src/shaders/titlebar.frag.glsl +++ b/src/shaders/titlebar.frag.glsl @@ -1,23 +1,14 @@ #version 450 -layout(location = 0) in vec2 fragTexCoord; -layout(location = 1) in vec4 fragColor; -layout(location = 2) in vec4 backgroundColor; -layout(location = 3) in flat int drawTexture; - +layout(location = 0) in vec3 fragColor; +layout(location = 1) in vec2 fragTexCoord; layout(binding = 0) uniform sampler2D texSampler; layout(location = 0) out vec4 outColor; -float median(float r, float g, float b) { - return max(min(r, g), min(max(r, g), b)); -} - void main() { - if (drawTexture == 1) { - vec4 texture = texture(texSampler, fragTexCoord); - outColor = mix(backgroundColor, fragColor, texture.a); - } else { - outColor = backgroundColor; - } -} \ No newline at end of file + vec4 background = vec4(fragColor, 1.0); + vec4 texture = texture(texSampler, fragTexCoord); + outColor = mix(background, vec4(1.0, 1.0, 1.0, 1.0), texture.a); + outColor = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/src/shaders/titlebar.vert.glsl b/src/shaders/titlebar.vert.glsl index 41c0824..eb84b7a 100644 --- a/src/shaders/titlebar.vert.glsl +++ b/src/shaders/titlebar.vert.glsl @@ -1,111 +1,20 @@ #version 450 layout (push_constant) uniform _push_constants { - vec4 minimizeColor; - vec4 maximizeColor; - vec4 closeColor; float aspectRatio; } PushConstants; -layout(location = 0) out vec2 fragTexCoord; -layout(location = 1) out vec4 fragColor; -layout(location = 2) out vec4 backgroundColor; -layout(location = 3) out flat int drawTexture; +layout(location = 0) in vec2 inPosition; +layout(location = 1) in vec3 inColor; +layout(location = 2) in vec2 inTexCoord; -float aspectRatio = PushConstants.aspectRatio; -float buttonWidth = 2.0 / aspectRatio; - -vec2 positions[] = vec2[]( - vec2(-1, -1), vec2(-1, 1), vec2(1, 1), vec2(1, -1), - vec2(1.0 - buttonWidth, -1), vec2(1.0 - buttonWidth, 1), vec2(1, 1), vec2(1, -1), - vec2(1.0 - buttonWidth * 2, -1), vec2(1.0 - buttonWidth * 2, 1), vec2(1 - buttonWidth, 1), vec2(1 - buttonWidth, -1), - vec2(1.0 - buttonWidth * 3, -1), vec2(1.0 - buttonWidth * 3, 1), vec2(1 - buttonWidth * 2, 1), vec2(1 - buttonWidth * 2, -1) -); +layout(location = 0) out vec3 fragColor; +layout(location = 1) out vec2 fragTexCoord; -int indices[] = int[]( - 0, 1, 2, 0, 2, 3, - 4, 5, 6, 4, 6, 7, - 8, 9, 10, 8, 10, 11, - 12, 13, 14, 12, 14, 15 -); +float aspectRatio = PushConstants.aspectRatio; void main() { - gl_Position = vec4(positions[indices[gl_VertexIndex]], 0.0, 1.0); - - if (gl_VertexIndex < 6) { - fragColor = vec4(0.0, 0.0, 0.0, 0.0); - backgroundColor = vec4(1.0, 1.0, 1.0, 0.01); - drawTexture = 0; - } else if (gl_VertexIndex < 12) { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); - backgroundColor = PushConstants.closeColor; - drawTexture = 1; - } else if (gl_VertexIndex < 18) { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); - backgroundColor = PushConstants.maximizeColor; - drawTexture = 1; - } else if (gl_VertexIndex < 24) { - fragColor = vec4(1.0, 1.0, 1.0, 1.0); - backgroundColor = PushConstants.minimizeColor; - drawTexture = 1; - } - - switch (gl_VertexIndex) { - case 6: - fragTexCoord = vec2(0.0, 0.0); - break; - case 7: - fragTexCoord = vec2(0.0, 0.5); - break; - case 8: - fragTexCoord = vec2(0.5, 0.5); - break; - case 9: - fragTexCoord = vec2(0.0, 0.0); - break; - case 10: - fragTexCoord = vec2(0.5, 0.5); - break; - case 11: - fragTexCoord = vec2(0.5, 0.0); - break; - case 12: - fragTexCoord = vec2(0.5, 0.0); - break; - case 13: - fragTexCoord = vec2(0.5, 0.5); - break; - case 14: - fragTexCoord = vec2(1.0, 0.5); - break; - case 15: - fragTexCoord = vec2(0.5, 0.0); - break; - case 16: - fragTexCoord = vec2(1.0, 0.5); - break; - case 17: - fragTexCoord = vec2(1.0, 0.0); - break; - case 18: - fragTexCoord = vec2(0.0, 0.5); - break; - case 19: - fragTexCoord = vec2(0.0, 1.0); - break; - case 20: - fragTexCoord = vec2(0.5, 1.0); - break; - case 21: - fragTexCoord = vec2(0.0, 0.5); - break; - case 22: - fragTexCoord = vec2(0.5, 1.0); - break; - case 23: - fragTexCoord = vec2(0.5, 0.5); - break; - default: - fragTexCoord = vec2(0.0, 0.5); - } + gl_Position = vec4(inPosition, 0.0, 1.0); + fragColor = inColor; + fragTexCoord = inTexCoord; } diff --git a/src/textures/titlebar.png b/src/textures/titlebar.png index e501572507f1d02e1ad95902cb33d37321aee1d5..1abd12e68dcce823bbfd90f6d8230d00e3b74a98 100644 GIT binary patch literal 1279 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=Awio-U3d6?5Lswau3a6=|EF ztmt?rz`?gWLz5|llj$S-57x2*myXp66DBk$c?7sD;y5IDuKm9|*PIZA2^oU9zeEIu zG&GAe(#&?prBA*)xB7j;-uKFD-^5nF+d1#^nZ3s8>&ldj4R_`A++{S@6LXN^R_J3D zIL_3O%y?uY!=ng>LY)Q+3}F#L1*LPYN6vPv&Ma4*qnQqD!ywB=h^ z{=V46KWBCg%brN1V+`k*;?4SI9!OyH;<@>}G28h4_C{&d<<0+2{GXWliIMNujWmX4 z_l7fPI(FRHzxTrbJ4_mevS)aI3-dK(1)p``Yur8cmL2n+=uHQOf*njb{SrPgzDs%b zREpRA$aN911vd=l-u?8bTV^ibwuoXYi-jlc3q|(kXsaGPGw1!2@MS+re+O`;O{!0P zmaM*CD3r_mwbz3u+m}Z@%?st6^QUk{)UMbtCiQxot{67E?&q;emqNG9`7?2a9)r!q z1o4WA>w-9HZ|I7XD>|z85E4=zq|i&UgrM$+nxEc51yDWeOcAO`kY1B z=1u>B2yUmOB|FmkmNDsQy!VUPos)BI`OS0hgsYnupWM%SXHMmQ)|l-qZ!?O7EQ;sa zS6?b;cyl3^)#lNQ&cv%zUDLcKo7UJoUKjUrcSoA(s&@}k^It|aeO$4vIDGH?e9mJF zPxZx?8@8)v_HY!VUx@v~|%cZxEUi%Q)WG1qb`AXD*$m)}os^?se zgm1m2cP}Z8uj=B}uj>u>Zk=5sy03R(?)jrPR!b%){NFe8b#(Xjr$V**hEv`Z@7WXm z<;9!j&9Qycd%t@h4On_auB_kX5}%!=}`M~P{5w=JF-Dm_YlU{)aYgZqbq@q68kclAEh{{MFQ%%8;0+_=}t zd-?ucJ6^RR?|$RM{+^etRSv)1m;JH*wm9k6|G%4e*2Tsq%?>W%-!~)YGslgXq}{8F zH`@KTw=r^?TiSjh=0kP&4S$WVUp~B-NaV_p$rrrd$ltiV(Y{gp{A~r}|GgdFKFb@t zXZ}1dv~eB7d**zG{Xq|Ade$X*C@MXgdS=i5M`p`k6m9VN#C|mD#x&Lo$DZTNzT}0} coc|2B@7=xi=pf4qVBy8!>FVdQ&MBb@0GeDlOaK4? literal 1115 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSoCO|{#S9F5he4R}c>anMprB-l zYeY$Kep*R+Vo@qXd3m{BW?pu2a$-TMUVc&f>~}U&3=GUKJY5_^D(1YM>zgO+D9{@3 z>an5m@CL^A;euwUQq<=B)?QG-!%btWUdsWw9`JE+r${$9FEa#59pR5CCtYSK&`Q9&b zSB}oK<+o1yb=n^L`eZ(n#Y>rfhMH+CUsn_f--vu(p8x9{L(bwz)<4I-N*`Wi-R)zUrjY!&uG(;v6_AQyMOzN&RDx&`OQ?vR59iL+_*nQ-`l4q z-?KDfogn=6+XsG$1g;e_@BJ2;w@p~ANldmO&CNfVtvezopr E06m_ + id="path4" + style="fill:#000000" /> diff --git a/src/titlebar.c b/src/titlebar.c index 9de0650..bb2e9b6 100644 --- a/src/titlebar.c +++ b/src/titlebar.c @@ -3,6 +3,7 @@ #include "lodepng.h" #include "titlebar.h" +#include "buffer.h" #include "descriptor.h" #include "image.h" #include "image_view.h" @@ -21,16 +22,36 @@ #include "../texture_titlebar.h" #endif /* EMBED_TEXTURES */ +#define TITLEBAR_VERTEX_COUNT 5 * 4 +#define TITLEBAR_INDEX_COUNT 5 * 6 + static const float VIEWPORT_WIDTH = 2.0f; static const float VIEWPORT_HEIGHT = 2.0f; typedef struct titlebar_push_constants_t { - float minimizeColor[4]; - float maximizeColor[4]; - float closeColor[4]; float aspectRatio; } TitlebarPushConstants; +typedef enum action_t { + TITLEBAR_ACTION_CLOSE, + TITLEBAR_ACTION_MAXIMIZE, + TITLEBAR_ACTION_MINIMIZE, + TITLEBAR_ACTION_MENU +} TITLEBAR_ACTION; + +float iconSpriteOriginMap[4][2] = { + {0.0f, 0.0f}, + {0.5f, 0.0f}, + {0.0f, 0.5f}, + {0.5f, 0.5f} +}; + +typedef struct board_vertex_t { + float pos[2]; + float color[3]; + float texCoord[2]; +} TitlebarVertex; + struct titlebar_t { VkDevice device; VmaAllocator allocator; @@ -52,9 +73,11 @@ struct titlebar_t { VkDescriptorSetLayout descriptorSetLayouts[MAX_FRAMES_IN_FLIGHT]; NormalizedPointerPosition pointerPosition; float aspectRatio; + bool hoveringMenu; bool hoveringMinimize; bool hoveringMaximize; bool hoveringClose; + bool pressedMenu; bool pressedMinimize; bool pressedMaximize; bool pressedClose; @@ -64,11 +87,26 @@ struct titlebar_t { void *maximizeArg; void (*minimize)(void *); void *minimizeArg; + VkRect2D buttonRectangles[4]; + TitlebarVertex vertices[TITLEBAR_VERTEX_COUNT]; + VkBuffer vertexBuffer; + VmaAllocation vertexBufferAllocation; + void *vertexBufferMappedMemory; + uint16_t indices[TITLEBAR_INDEX_COUNT]; + VkBuffer indexBuffer; + VmaAllocation indexBufferAllocation; + void *indexBufferMappedMemory; }; static bool createTitlebarTexture(Titlebar self, char **error); static bool createTitlebarTextureSampler(Titlebar self, char **error); static bool createTitlebarDescriptors(Titlebar self, char **error); +static void updateMesh(Titlebar self); +static bool createVertexBuffer(Titlebar self, char **error); +static void updateVertexBuffer(Titlebar self); +static void updateIndices(Titlebar self); +static bool createIndexBuffer(Titlebar self, char **error); +static void updateIndexBuffer(Titlebar self); static bool createTitlebarPipeline(Titlebar self, char **error); static void updateHovering(Titlebar self); static void updatePressed(Titlebar self); @@ -88,9 +126,14 @@ bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, self->sampleCount = sampleCount; self->resourcePath = resourcePath; self->aspectRatio = aspectRatio; + self->hoveringMenu = false; self->hoveringMinimize = false; self->hoveringMaximize = false; self->hoveringClose = false; + self->pressedMenu = false; + self->pressedMinimize = false; + self->pressedMaximize = false; + self->hoveringClose = false; self->close = close; self->closeArg = closeArg; self->maximize = maximize; @@ -98,6 +141,17 @@ bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, self->minimize = minimize; self->minimizeArg = minimizeArg; + updateMesh(self); + updateIndices(self); + + if (!createVertexBuffer(self, error)) { + return false; + } + + if (!createIndexBuffer(self, error)) { + return false; + } + if (!createTitlebarTexture(self, error)) { return false; } @@ -192,6 +246,113 @@ static bool createTitlebarTexture(Titlebar self, char **error) return true; } +static void updateButtonRectangles(Titlebar self) +{ + float buttonHeight = 2.0f; + float buttonWidth = 2.0f; + + self->buttonRectangles[TITLEBAR_ACTION_CLOSE] = (VkRect2D) { + .offset = { + .x = 1.0 - buttonWidth, + .y = 1.0 - buttonHeight + }, + .extent = { + .width = buttonWidth, + .height = buttonHeight + } + }; + + self->buttonRectangles[TITLEBAR_ACTION_MAXIMIZE] = (VkRect2D) { + .offset = { + .x = 1.0 - buttonWidth * 2, + .y = 1.0 - buttonHeight + }, + .extent = { + .width = buttonWidth, + .height = buttonHeight + } + }; + + self->buttonRectangles[TITLEBAR_ACTION_MINIMIZE] = (VkRect2D) { + .offset = { + .x = 1.0 - buttonWidth * 3, + .y = 1.0 - buttonHeight + }, + .extent = { + .width = buttonWidth, + .height = buttonHeight + } + }; + + self->buttonRectangles[TITLEBAR_ACTION_MENU] = (VkRect2D) { + .offset = { + .x = 1.0 - buttonWidth * 4, + .y = 1.0 - buttonHeight + }, + .extent = { + .width = buttonWidth, + .height = buttonHeight + } + }; +} + +static void updateMesh(Titlebar self) +{ + float hoverColor[] = {1.0f, 1.0f, 1.0f, 0.01f}; + float pressedColor[] = {1.0f, 1.0f, 1.0f, 0.05f}; + + float buttonHeight = 2.0f; + float buttonWidth = 2.0f; + + float barColor[] = {0.0f, 0.5f, 0.5f}; + srgbToLinear(barColor); + float defaultBackground[] = {0.71f, 0.533f, 0.388f}; + srgbToLinear(defaultBackground); + float hoverBackground[] = {0.71f, 0.533f, 0.388f}; + srgbToLinear(hoverBackground); + + bool hovering[] = {self->hoveringClose, self->hoveringMaximize, self->hoveringMinimize, self->hoveringMenu}; + float buttonColors[4][3]; + for (size_t i = 0; i <= TITLEBAR_ACTION_MENU; ++i) { + if (hovering[i]) { + buttonColors[i][0] = hoverBackground[0]; + buttonColors[i][1] = hoverBackground[1]; + buttonColors[i][2] = hoverBackground[2]; + } else { + buttonColors[i][0] = defaultBackground[0]; + buttonColors[i][0] = defaultBackground[1]; + buttonColors[i][0] = defaultBackground[2]; + } + } + + self->vertices[0] = (TitlebarVertex) {{-1.0f, -1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; + self->vertices[1] = (TitlebarVertex) {{-1.0f, 1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; + self->vertices[2] = (TitlebarVertex) {{1.0f, 1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; + self->vertices[3] = (TitlebarVertex) {{1.0f, -1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; + for (size_t i = 0; i <= TITLEBAR_ACTION_MENU; ++i) { + self->vertices[(i + 1) * 4] = (TitlebarVertex) { + {self->buttonRectangles[i].offset.x, self->buttonRectangles[i].offset.y}, + {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, + {iconSpriteOriginMap[i][0], iconSpriteOriginMap[i][1]} + }; + self->vertices[(i + 1) * 4 + 1] = (TitlebarVertex) { + {self->buttonRectangles[i].offset.x, self->buttonRectangles[i].offset.y + buttonHeight}, + {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, + {iconSpriteOriginMap[i][0], iconSpriteOriginMap[i][1] + 0.5f} + }; + self->vertices[(i + 1) * 4 + 2] = (TitlebarVertex) { + {self->buttonRectangles[i].offset.x + buttonWidth, self->buttonRectangles[i].offset.y + buttonHeight}, + {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, + {iconSpriteOriginMap[i][0] + 0.5f, iconSpriteOriginMap[i][1] + 0.5f} + }; + self->vertices[(i + 1) * 4 + 3] = (TitlebarVertex) { + {self->buttonRectangles[i].offset.x + buttonWidth, self->buttonRectangles[i].offset.y}, + {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, + {iconSpriteOriginMap[i][0] + 0.5f, iconSpriteOriginMap[i][1]} + }; + } +} + static bool createTitlebarTextureSampler(Titlebar self, char **error) { if (!createSampler(self->device, 0, 1, &self->sampler, error)) { @@ -238,8 +399,76 @@ static bool createTitlebarDescriptors(Titlebar self, char **error) return true; } +static bool createVertexBuffer(Titlebar self, char **error) +{ + if (!createHostVisibleMutableBuffer(self->device, self->allocator, self->commandPool, self->queue, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, &self->vertexBufferMappedMemory, &self->vertexBuffer, &self->vertexBufferAllocation, self->vertices, TITLEBAR_VERTEX_COUNT, sizeof(*self->vertices), error)) { + return false; + } + + return true; +} + +static void updateVertexBuffer(Titlebar self) +{ + updateHostVisibleMutableBuffer(self->device, self->vertexBufferMappedMemory, &self->vertexBuffer, TITLEBAR_VERTEX_COUNT, sizeof(*self->vertices)); +} + +static void updateIndices(Titlebar self) +{ + for (size_t i = 0; i < 5; ++i) { + size_t verticesOffset = i * 4; + size_t indicesOffset = i * 6; + + self->indices[indicesOffset] = verticesOffset + 0; + self->indices[indicesOffset + 1] = verticesOffset + 1; + self->indices[indicesOffset + 2] = verticesOffset + 2; + self->indices[indicesOffset + 3] = verticesOffset + 0; + self->indices[indicesOffset + 4] = verticesOffset + 2; + self->indices[indicesOffset + 5] = verticesOffset + 3; + } +} + +static bool createIndexBuffer(Titlebar self, char **error) +{ + if (!createHostVisibleMutableBuffer(self->device, self->allocator, self->commandPool, self->queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, &self->indexBufferMappedMemory, &self->indexBuffer, &self->indexBufferAllocation, self->indices, TITLEBAR_INDEX_COUNT, sizeof(*self->indices), error)) { + return false; + } + + return true; +} + +static void updateIndexBuffer(Titlebar self) +{ + updateHostVisibleMutableBuffer(self->device, self->indexBufferMappedMemory, &self->indexBuffer, TITLEBAR_INDEX_COUNT, sizeof(*self->indices)); +} + static bool createTitlebarPipeline(Titlebar self, char **error) { + VkVertexInputBindingDescription vertexBindingDescription = { + .binding = 0, + .stride = sizeof(TitlebarVertex), + .inputRate = VK_VERTEX_INPUT_RATE_VERTEX + }; + + VkVertexInputAttributeDescription vertexAttributeDescriptions[] = { + { + .binding = 0, + .location = 0, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(TitlebarVertex, pos), + }, { + .binding = 0, + .location = 1, + .format = VK_FORMAT_R32G32B32_SFLOAT, + .offset = offsetof(TitlebarVertex, color), + }, { + .binding = 0, + .location = 2, + .format = VK_FORMAT_R32G32_SFLOAT, + .offset = offsetof(TitlebarVertex, texCoord), + } + }; + #ifndef EMBED_SHADERS char *titlebarVertShaderPath; char *titlebarFragShaderPath; @@ -287,10 +516,10 @@ static bool createTitlebarPipeline(Titlebar self, char **error) .vertexShaderSize = titlebarVertShaderSize, .fragmentShaderBytes = titlebarFragShaderBytes, .fragmentShaderSize = titlebarFragShaderSize, - .vertexBindingDescriptionCount = 0, - .vertexBindingDescriptions = NULL, - .vertexAttributeDescriptionCount = 0, - .VertexAttributeDescriptions = NULL, + .vertexBindingDescriptionCount = 1, + .vertexBindingDescriptions = &vertexBindingDescription, + .vertexAttributeDescriptionCount = sizeof(vertexAttributeDescriptions) / sizeof(*vertexAttributeDescriptions), + .VertexAttributeDescriptions = vertexAttributeDescriptions, .descriptorSetLayouts = self->descriptorSetLayouts, .descriptorSetLayoutCount = 1, .pushConstantRangeCount = 1, @@ -315,6 +544,7 @@ static void updateHovering(Titlebar self) self->hoveringClose = false; self->hoveringMaximize = false; self->hoveringMinimize = false; + self->hoveringMenu = false; if (self->pointerPosition.x >= 1.0f - (1.0f / self->aspectRatio)) { self->hoveringClose = true; @@ -357,35 +587,17 @@ static void handleButtonUp(Titlebar self) bool drawTitlebar(Titlebar self, VkCommandBuffer commandBuffer, char **error) { - float hoverColor[] = {1.0f, 1.0f, 1.0f, 0.01f}; - float pressedColor[] = {1.0f, 1.0f, 1.0f, 0.05f}; - TitlebarPushConstants pushConstants = { - .minimizeColor = {0.0f, 0.0f, 0.0f, 0.0f}, - .maximizeColor = {0.0f, 0.0f, 0.0f, 0.0f}, - .closeColor = {0.0f, 0.0f, 0.0f, 0.0f}, .aspectRatio = self->aspectRatio }; - if (self->pressedClose) { - vec4Copy(pressedColor, pushConstants.closeColor); - } else if (self->pressedMaximize) { - vec4Copy(pressedColor, pushConstants.maximizeColor); - } else if (self->pressedMinimize) { - vec4Copy(pressedColor, pushConstants.minimizeColor); - } else if (self->hoveringClose) { - vec4Copy(hoverColor, pushConstants.closeColor); - } else if (self->hoveringMaximize) { - vec4Copy(hoverColor, pushConstants.maximizeColor); - } else if (self->hoveringMinimize) { - vec4Copy(hoverColor, pushConstants.minimizeColor); - } - VkDeviceSize offsets[] = {0}; + vkCmdBindVertexBuffers(commandBuffer, 0, 1, &self->vertexBuffer, offsets); + vkCmdBindIndexBuffer(commandBuffer, self->indexBuffer, 0, VK_INDEX_TYPE_UINT16); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipeline); vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, self->pipelineLayout, 0, 1, self->descriptorSets, 0, NULL); vkCmdPushConstants(commandBuffer, self->pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(pushConstants), &pushConstants); - vkCmdDraw(commandBuffer, 24, 1, 0, 0); + vkCmdDrawIndexed(commandBuffer, TITLEBAR_INDEX_COUNT, 1, 0, 0, 0); return true; } @@ -430,4 +642,5 @@ void destroyTitlebar(Titlebar self) void titlebarSetAspectRatio(Titlebar self, float aspectRatio) { self->aspectRatio = aspectRatio; + updateVertexBuffer(self); } diff --git a/xcode/modeler/ModelerApp.swift b/xcode/modeler/ModelerApp.swift index c5f092f..d24d625 100644 --- a/xcode/modeler/ModelerApp.swift +++ b/xcode/modeler/ModelerApp.swift @@ -5,7 +5,7 @@ struct ModelerApp: App { var body: some Scene { WindowGroup { GeometryReader { geometry in - ModelerViewControllerRepresentable(titlebarHeight: Float(geometry.safeAreaInsets.top)) + ModelerViewControllerRepresentable(titlebarHeight: Float(30)) .ignoresSafeArea() } }.windowStyle(.hiddenTitleBar) From 7da815bbea64a2ba07293f050fa224711da9ce08 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Wed, 26 Nov 2025 09:04:06 -0800 Subject: [PATCH 02/17] fix: put size and count args in correct order --- src/chess_board.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chess_board.c b/src/chess_board.c index 28f67df..75f4ca1 100644 --- a/src/chess_board.c +++ b/src/chess_board.c @@ -602,7 +602,7 @@ static bool createBoardIndexBuffer(ChessBoard self, char **error) indices[indicesOffset + 5] = verticesOffset + 0; } - if (!createStaticBuffer(self->device, self->allocator, self->commandPool, self->queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, &self->boardIndexBuffer, &self->boardIndexBufferAllocation, indices, sizeof(indices[0]), CHESS_INDEX_COUNT + 48, error)) { + if (!createStaticBuffer(self->device, self->allocator, self->commandPool, self->queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, &self->boardIndexBuffer, &self->boardIndexBufferAllocation, indices, CHESS_INDEX_COUNT + 48, sizeof(indices[0]), error)) { return false; } From 8495260d7e6c287a683a814953a84fa3bcb6b351 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Wed, 26 Nov 2025 09:04:22 -0800 Subject: [PATCH 03/17] style: remove trailing commas --- src/titlebar.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/titlebar.c b/src/titlebar.c index bb2e9b6..a3925f2 100644 --- a/src/titlebar.c +++ b/src/titlebar.c @@ -455,17 +455,17 @@ static bool createTitlebarPipeline(Titlebar self, char **error) .binding = 0, .location = 0, .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(TitlebarVertex, pos), + .offset = offsetof(TitlebarVertex, pos) }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, - .offset = offsetof(TitlebarVertex, color), + .offset = offsetof(TitlebarVertex, color) }, { .binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, - .offset = offsetof(TitlebarVertex, texCoord), + .offset = offsetof(TitlebarVertex, texCoord) } }; From 0d12a23ebdc0da9fed08fe21218d33c4927b0f07 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Wed, 26 Nov 2025 09:04:38 -0800 Subject: [PATCH 04/17] feat: draw with actual frag color --- src/shaders/titlebar.frag.glsl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/shaders/titlebar.frag.glsl b/src/shaders/titlebar.frag.glsl index 7769d60..061d21a 100644 --- a/src/shaders/titlebar.frag.glsl +++ b/src/shaders/titlebar.frag.glsl @@ -10,5 +10,4 @@ void main() { vec4 background = vec4(fragColor, 1.0); vec4 texture = texture(texSampler, fragTexCoord); outColor = mix(background, vec4(1.0, 1.0, 1.0, 1.0), texture.a); - outColor = vec4(1.0, 1.0, 1.0, 1.0); } From 29ade16b12f2ce7708e0f685e4f8abdab38cf11b Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Wed, 26 Nov 2025 09:14:20 -0800 Subject: [PATCH 05/17] fix: provide correct args to update buffer --- src/titlebar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/titlebar.c b/src/titlebar.c index a3925f2..0d485f5 100644 --- a/src/titlebar.c +++ b/src/titlebar.c @@ -410,7 +410,7 @@ static bool createVertexBuffer(Titlebar self, char **error) static void updateVertexBuffer(Titlebar self) { - updateHostVisibleMutableBuffer(self->device, self->vertexBufferMappedMemory, &self->vertexBuffer, TITLEBAR_VERTEX_COUNT, sizeof(*self->vertices)); + updateHostVisibleMutableBuffer(self->device, self->vertexBufferMappedMemory, self->vertices, TITLEBAR_VERTEX_COUNT, sizeof(*self->vertices)); } static void updateIndices(Titlebar self) @@ -439,7 +439,7 @@ static bool createIndexBuffer(Titlebar self, char **error) static void updateIndexBuffer(Titlebar self) { - updateHostVisibleMutableBuffer(self->device, self->indexBufferMappedMemory, &self->indexBuffer, TITLEBAR_INDEX_COUNT, sizeof(*self->indices)); + updateHostVisibleMutableBuffer(self->device, self->indexBufferMappedMemory, self->indices, TITLEBAR_INDEX_COUNT, sizeof(*self->indices)); } static bool createTitlebarPipeline(Titlebar self, char **error) From e86ea4323d82ce3e5027fc7e4fca4e9610732798 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Wed, 26 Nov 2025 22:37:03 -0800 Subject: [PATCH 06/17] feat: create button meshes --- src/component.h | 19 +++++++++++++ src/modeler.c | 4 +-- src/titlebar.c | 75 +++++++++++++++++++++++++++---------------------- src/titlebar.h | 2 +- 4 files changed, 63 insertions(+), 37 deletions(-) create mode 100644 src/component.h diff --git a/src/component.h b/src/component.h new file mode 100644 index 0000000..378f585 --- /dev/null +++ b/src/component.h @@ -0,0 +1,19 @@ +#ifndef MODELER_COMPONENT_H +#define MODELER_COMPONENT_H + +typedef struct offset_t { + float x; + float y; +} Offset; + +typedef struct extent_t { + float width; + float height; +} Extent; + +typedef struct normalized_rectangle_t { + Offset offset; + Extent extent; +} NormalizedRectangle; + +#endif /* MODELER_COMPONENT_H */ diff --git a/src/modeler.c b/src/modeler.c index 1f6edd9..f4a49a8 100644 --- a/src/modeler.c +++ b/src/modeler.c @@ -218,14 +218,14 @@ void *threadProc(void *arg) } float aspectRatio = (float) windowDimensions.activeArea.extent.width / windowDimensions.titlebarHeight; - float titlebarHeight = (float) windowDimensions.titlebarHeight / windowDimensions.activeArea.extent.height; + float titlebarHeight = (float) windowDimensions.titlebarHeight / (windowDimensions.titlebarHeight + windowDimensions.insets.top); #ifdef ENABLE_IMGUI int titlebarSubpass = 2; #else /* ENABLE_IMGUI */ int titlebarSubpass = 1; #endif /* ENABLE_IMGUI */ Titlebar titlebar; - if (!createTitlebar(&titlebar, device, allocator, commandPool, queueInfo.graphicsQueue, renderPass, titlebarSubpass, getMaxSampleCount(physicalDeviceCharacteristics.deviceProperties), resourcePath, aspectRatio, &sendCloseSignal, platformWindow, &sendMaximizeSignal, platformWindow, &sendMinimizeSignal, platformWindow, error)) { + if (!createTitlebar(&titlebar, device, allocator, commandPool, queueInfo.graphicsQueue, renderPass, titlebarSubpass, getMaxSampleCount(physicalDeviceCharacteristics.deviceProperties), resourcePath, aspectRatio, titlebarHeight, &sendCloseSignal, platformWindow, &sendMaximizeSignal, platformWindow, &sendMinimizeSignal, platformWindow, error)) { sendThreadFailureSignal(platformWindow); } diff --git a/src/titlebar.c b/src/titlebar.c index 0d485f5..33bf23e 100644 --- a/src/titlebar.c +++ b/src/titlebar.c @@ -3,6 +3,7 @@ #include "lodepng.h" #include "titlebar.h" +#include "component.h" #include "buffer.h" #include "descriptor.h" #include "image.h" @@ -39,7 +40,7 @@ typedef enum action_t { TITLEBAR_ACTION_MENU } TITLEBAR_ACTION; -float iconSpriteOriginMap[4][2] = { +float actionSpriteOriginMap[4][2] = { {0.0f, 0.0f}, {0.5f, 0.0f}, {0.0f, 0.5f}, @@ -73,6 +74,7 @@ struct titlebar_t { VkDescriptorSetLayout descriptorSetLayouts[MAX_FRAMES_IN_FLIGHT]; NormalizedPointerPosition pointerPosition; float aspectRatio; + float height; bool hoveringMenu; bool hoveringMinimize; bool hoveringMaximize; @@ -87,7 +89,7 @@ struct titlebar_t { void *maximizeArg; void (*minimize)(void *); void *minimizeArg; - VkRect2D buttonRectangles[4]; + NormalizedRectangle buttonRectangles[4]; TitlebarVertex vertices[TITLEBAR_VERTEX_COUNT]; VkBuffer vertexBuffer; VmaAllocation vertexBufferAllocation; @@ -101,6 +103,7 @@ struct titlebar_t { static bool createTitlebarTexture(Titlebar self, char **error); static bool createTitlebarTextureSampler(Titlebar self, char **error); static bool createTitlebarDescriptors(Titlebar self, char **error); +static void updateButtonRectangles(Titlebar self); static void updateMesh(Titlebar self); static bool createVertexBuffer(Titlebar self, char **error); static void updateVertexBuffer(Titlebar self); @@ -111,7 +114,7 @@ static bool createTitlebarPipeline(Titlebar self, char **error); static void updateHovering(Titlebar self); static void updatePressed(Titlebar self); -bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, VkCommandPool commandPool, VkQueue queue, VkRenderPass renderPass, uint32_t subpass, VkSampleCountFlagBits sampleCount, const char *resourcePath, float aspectRatio, void (*close)(void *), void *closeArg, void (*maximize)(void *), void *maximizeArg, void (*minimize)(void *), void *minimizeArg, char **error) +bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, VkCommandPool commandPool, VkQueue queue, VkRenderPass renderPass, uint32_t subpass, VkSampleCountFlagBits sampleCount, const char *resourcePath, float aspectRatio, float height, void (*close)(void *), void *closeArg, void (*maximize)(void *), void *maximizeArg, void (*minimize)(void *), void *minimizeArg, char **error) { *titlebar = malloc(sizeof(**titlebar)); @@ -126,6 +129,7 @@ bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, self->sampleCount = sampleCount; self->resourcePath = resourcePath; self->aspectRatio = aspectRatio; + self->height = height; self->hoveringMenu = false; self->hoveringMinimize = false; self->hoveringMaximize = false; @@ -141,6 +145,7 @@ bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, self->minimize = minimize; self->minimizeArg = minimizeArg; + updateButtonRectangles(self); updateMesh(self); updateIndices(self); @@ -248,13 +253,13 @@ static bool createTitlebarTexture(Titlebar self, char **error) static void updateButtonRectangles(Titlebar self) { - float buttonHeight = 2.0f; - float buttonWidth = 2.0f; + float buttonHeight = self->height * 2; + float buttonWidth = buttonHeight / self->aspectRatio; - self->buttonRectangles[TITLEBAR_ACTION_CLOSE] = (VkRect2D) { + self->buttonRectangles[TITLEBAR_ACTION_CLOSE] = (NormalizedRectangle) { .offset = { - .x = 1.0 - buttonWidth, - .y = 1.0 - buttonHeight + .x = 1.0f - buttonWidth, + .y = 1.0f - buttonHeight }, .extent = { .width = buttonWidth, @@ -262,10 +267,10 @@ static void updateButtonRectangles(Titlebar self) } }; - self->buttonRectangles[TITLEBAR_ACTION_MAXIMIZE] = (VkRect2D) { + self->buttonRectangles[TITLEBAR_ACTION_MAXIMIZE] = (NormalizedRectangle) { .offset = { - .x = 1.0 - buttonWidth * 2, - .y = 1.0 - buttonHeight + .x = 1.0f - buttonWidth * 2, + .y = 1.0f - buttonHeight }, .extent = { .width = buttonWidth, @@ -273,10 +278,10 @@ static void updateButtonRectangles(Titlebar self) } }; - self->buttonRectangles[TITLEBAR_ACTION_MINIMIZE] = (VkRect2D) { + self->buttonRectangles[TITLEBAR_ACTION_MINIMIZE] = (NormalizedRectangle) { .offset = { - .x = 1.0 - buttonWidth * 3, - .y = 1.0 - buttonHeight + .x = 1.0f - buttonWidth * 3, + .y = 1.0f - buttonHeight }, .extent = { .width = buttonWidth, @@ -284,10 +289,10 @@ static void updateButtonRectangles(Titlebar self) } }; - self->buttonRectangles[TITLEBAR_ACTION_MENU] = (VkRect2D) { + self->buttonRectangles[TITLEBAR_ACTION_MENU] = (NormalizedRectangle) { .offset = { - .x = 1.0 - buttonWidth * 4, - .y = 1.0 - buttonHeight + .x = 1.0f - buttonWidth * 4, + .y = 1.0f - buttonHeight }, .extent = { .width = buttonWidth, @@ -301,9 +306,6 @@ static void updateMesh(Titlebar self) float hoverColor[] = {1.0f, 1.0f, 1.0f, 0.01f}; float pressedColor[] = {1.0f, 1.0f, 1.0f, 0.05f}; - float buttonHeight = 2.0f; - float buttonWidth = 2.0f; - float barColor[] = {0.0f, 0.5f, 0.5f}; srgbToLinear(barColor); float defaultBackground[] = {0.71f, 0.533f, 0.388f}; @@ -320,8 +322,8 @@ static void updateMesh(Titlebar self) buttonColors[i][2] = hoverBackground[2]; } else { buttonColors[i][0] = defaultBackground[0]; - buttonColors[i][0] = defaultBackground[1]; - buttonColors[i][0] = defaultBackground[2]; + buttonColors[i][1] = defaultBackground[1]; + buttonColors[i][2] = defaultBackground[2]; } } @@ -330,25 +332,28 @@ static void updateMesh(Titlebar self) self->vertices[2] = (TitlebarVertex) {{1.0f, 1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; self->vertices[3] = (TitlebarVertex) {{1.0f, -1.0f}, {barColor[0], barColor[1], barColor[2]}, {0.0f, 0.0f}}; for (size_t i = 0; i <= TITLEBAR_ACTION_MENU; ++i) { + NormalizedRectangle rectangle = self->buttonRectangles[i]; + float *color = buttonColors[i]; + float *spriteOrigin = actionSpriteOriginMap[i]; self->vertices[(i + 1) * 4] = (TitlebarVertex) { - {self->buttonRectangles[i].offset.x, self->buttonRectangles[i].offset.y}, - {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, - {iconSpriteOriginMap[i][0], iconSpriteOriginMap[i][1]} + {rectangle.offset.x, rectangle.offset.y}, + {color[0], color[1], color[2]}, + {spriteOrigin[0], spriteOrigin[1]} }; self->vertices[(i + 1) * 4 + 1] = (TitlebarVertex) { - {self->buttonRectangles[i].offset.x, self->buttonRectangles[i].offset.y + buttonHeight}, - {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, - {iconSpriteOriginMap[i][0], iconSpriteOriginMap[i][1] + 0.5f} + {rectangle.offset.x, rectangle.offset.y + rectangle.extent.height}, + {color[0], color[1], color[2]}, + {spriteOrigin[0], spriteOrigin[1] + 0.5f} }; self->vertices[(i + 1) * 4 + 2] = (TitlebarVertex) { - {self->buttonRectangles[i].offset.x + buttonWidth, self->buttonRectangles[i].offset.y + buttonHeight}, - {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, - {iconSpriteOriginMap[i][0] + 0.5f, iconSpriteOriginMap[i][1] + 0.5f} + {rectangle.offset.x + rectangle.extent.width, rectangle.offset.y + rectangle.extent.height}, + {color[0], color[1], color[2]}, + {spriteOrigin[0] + 0.5f, spriteOrigin[1] + 0.5f} }; self->vertices[(i + 1) * 4 + 3] = (TitlebarVertex) { - {self->buttonRectangles[i].offset.x + buttonWidth, self->buttonRectangles[i].offset.y}, - {buttonColors[i][0], buttonColors[i][1], buttonColors[i][2]}, - {iconSpriteOriginMap[i][0] + 0.5f, iconSpriteOriginMap[i][1]} + {rectangle.offset.x + rectangle.extent.width, rectangle.offset.y}, + {color[0], color[1], color[2]}, + {spriteOrigin[0] + 0.5f, spriteOrigin[1]} }; } } @@ -642,5 +647,7 @@ void destroyTitlebar(Titlebar self) void titlebarSetAspectRatio(Titlebar self, float aspectRatio) { self->aspectRatio = aspectRatio; + updateButtonRectangles(self); + updateMesh(self); updateVertexBuffer(self); } diff --git a/src/titlebar.h b/src/titlebar.h index 24fa0aa..8af2747 100644 --- a/src/titlebar.h +++ b/src/titlebar.h @@ -11,7 +11,7 @@ typedef struct titlebar_t *Titlebar; #include "vulkan_utils.h" #include "vk_mem_alloc.h" -bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, VkCommandPool commandPool, VkQueue queue, VkRenderPass renderPass, uint32_t subpass, VkSampleCountFlagBits sampleCount, const char *resourcePath, float aspectRatio, void (*close)(void *), void *closeArg, void (*maximize)(void *), void *maximizeArg, void (*minimize)(void *), void *minimizeArg, char **error); +bool createTitlebar(Titlebar *titlebar, VkDevice device, VmaAllocator allocator, VkCommandPool commandPool, VkQueue queue, VkRenderPass renderPass, uint32_t subpass, VkSampleCountFlagBits sampleCount, const char *resourcePath, float aspectRatio, float height, void (*close)(void *), void *closeArg, void (*maximize)(void *), void *maximizeArg, void (*minimize)(void *), void *minimizeArg, char **error); bool drawTitlebar(Titlebar self, VkCommandBuffer commandBuffer, char **error); void destroyTitlebar(Titlebar self); void titlebarHandleInputEvent(void *titlebar, InputEvent *inputEvent); From bee749d5edae68ae4cdb40dd0188ccb95726d433 Mon Sep 17 00:00:00 2001 From: Chase Patterson Date: Fri, 29 May 2026 11:50:26 -0700 Subject: [PATCH 07/17] feat: change app name to chess --- .idea/codeStyles/Project.xml | 99 ++++++++++++++++- .idea/editor.xml | 101 ++++++++++++++++++ .idea/modules.xml | 8 -- android/app/build.gradle.kts | 8 +- .../java/io/abalog/modeler/MainActivity.kt | 2 +- android/app/src/main/res/values/strings.xml | 2 +- 6 files changed, 207 insertions(+), 13 deletions(-) create mode 100644 .idea/editor.xml delete mode 100644 .idea/modules.xml diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 8ad94fe..0b89945 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -4,6 +4,104 @@