From e93a83812ed33341e4f08fd58d6dbfdf5989fd80 Mon Sep 17 00:00:00 2001 From: Nic Barker Date: Wed, 21 May 2025 13:35:10 +1200 Subject: [PATCH] Update to latest clay API, reuse example and fix a couple of small issues --- examples/terminal-example/main.c | 60 ++---- renderers/terminal/clay_renderer_terminal.c | 190 ----------------- .../terminal/clay_renderer_terminal_ansi.c | 195 ++++++++++++++++++ 3 files changed, 216 insertions(+), 229 deletions(-) delete mode 100644 renderers/terminal/clay_renderer_terminal.c create mode 100644 renderers/terminal/clay_renderer_terminal_ansi.c diff --git a/examples/terminal-example/main.c b/examples/terminal-example/main.c index edbf349..b77e7bf 100644 --- a/examples/terminal-example/main.c +++ b/examples/terminal-example/main.c @@ -3,55 +3,37 @@ #include #include "../../clay.h" -#include "../../renderers/terminal/clay_renderer_terminal.c" +#include "../../renderers/terminal/clay_renderer_terminal_ansi.c" +#include "../shared-layouts/clay-video-demo.c" const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; -// An example function to begin the "root" of your layout tree -Clay_RenderCommandArray CreateLayout() { - Clay_BeginLayout(); - - CLAY(CLAY_ID("OuterContainer"), - CLAY_LAYOUT({.layoutDirection = CLAY_LEFT_TO_RIGHT, .sizing = {CLAY_SIZING_GROW(), CLAY_SIZING_GROW()}, }), - CLAY_RECTANGLE({ .color = {0,0,0,255} })) { - CLAY(CLAY_ID("SideBar"), - CLAY_LAYOUT({.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_PERCENT( - 0.5), .height = CLAY_SIZING_PERCENT(1)}}), - CLAY_RECTANGLE({.color = (Clay_Color) {255, 255, 255, 255}}) - ) { - } - CLAY(CLAY_ID("OtherSideBar"), - CLAY_LAYOUT({.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_PERCENT( - 0.5), .height = CLAY_SIZING_PERCENT(1)}}), - CLAY_RECTANGLE({ .color = {0,0, 0, 255 }}) - ) { - // TODO font size is wrong, only one is allowed, but I don't know which it is - CLAY_TEXT(CLAY_STRING("0123456789 0123456 78901 234567 89012 34567 8901234567890 123456789"), - CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24, .textColor = {255,255,255,255} })); - } - } - - return Clay_EndLayout(); +void HandleClayErrors(Clay_ErrorData errorData) { + printf("%s", errorData.errorText.chars); } int main() { - const int width = 80; - const int height = 24; + const int width = 145; + const int height = 41; + int columnWidth = 16; - uint64_t totalMemorySize = Clay_MinMemorySize(); - Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); - Clay_Initialize(arena, (Clay_Dimensions) { .width = (float) width, .height = (float) height }); // TODO this is wrong, but I have no idea what the actual size of the terminal is in pixels - // Tell clay how to measure text - Clay_SetMeasureTextFunction(Console_MeasureText); + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + Clay_Initialize(arena, + (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, + (Clay_ErrorHandler) {HandleClayErrors}); + // Tell clay how to measure text + Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); + ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); - while(true) { - Clay_RenderCommandArray layout = CreateLayout(); + while (true) { + Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); - Clay_Console_Render(layout, width, height); + Clay_Console_Render(renderCommands, width, height, columnWidth); - fflush(stdout); - sleep(1); - } + fflush(stdout); + sleep(1); + } } \ No newline at end of file diff --git a/renderers/terminal/clay_renderer_terminal.c b/renderers/terminal/clay_renderer_terminal.c deleted file mode 100644 index fb8a650..0000000 --- a/renderers/terminal/clay_renderer_terminal.c +++ /dev/null @@ -1,190 +0,0 @@ -#include "stdint.h" -#include "string.h" -#include "stdio.h" -#include "stdlib.h" -#include "math.h" -#ifdef CLAY_OVERFLOW_TRAP -#include "signal.h" -#endif - -static inline void Console_MoveCursor(int x, int y) { - printf("\033[%d;%dH", y+1, x+1); -} - -bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { - // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want - // TODO to expose Clay__PointIsInsideRect - return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height; -} - -static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, - Clay_BoundingBox scissorBox) { - if (color.r < 127 || color.g < 127 || color.b < 127 || color.a < 127) { - // For now there are only two colors, - return; - } - - for (int y = y0; y < height; y++) { - for (int x = x0; x < width; x++) { - if(!Clay_PointIsInsideRect((Clay_Vector2) { .x = x, .y = y }, scissorBox)) { - continue; - } - - Console_MoveCursor(x, y); - // TODO there are only two colors actually drawn, the background and white - if (color.r < 127 || color.g < 127 || color.b < 127 || color.a < 127) { - printf(" "); - } else { - printf("▪"); - } - } - } -} - -static inline Clay_Dimensions Console_MeasureText(Clay_String *text, Clay_TextElementConfig *config) { - Clay_Dimensions textSize = { 0 }; - - // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels - - float maxTextWidth = 0.0f; - float lineTextWidth = 0; - - float textHeight = 1; - - for (int i = 0; i < text->length; ++i) - { - if (text->chars[i] == '\n') { - maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; - lineTextWidth = 0; - textHeight++; - continue; - } - lineTextWidth++; - } - - maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; - - textSize.width = maxTextWidth; - textSize.height = textHeight; - - return textSize; -} - -void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) { - //TODO -} - -void Clay_Console_Render(Clay_RenderCommandArray renderCommands, int width, int height) -{ - printf("\033[H\033[J"); // Clear - - const Clay_BoundingBox fullWindow = { - .x = 0, - .y = 0, - .width = (float) width, - .height = (float) height, - }; - - Clay_BoundingBox scissorBox = fullWindow; - - for (int j = 0; j < renderCommands.length; j++) - { - Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); - Clay_BoundingBox boundingBox = renderCommand->boundingBox; - switch (renderCommand->commandType) - { - case CLAY_RENDER_COMMAND_TYPE_TEXT: { - Clay_String text = renderCommand->text; - int y = 0; - for (int x = 0; x < text.length; x++) { - if(text.chars[x] == '\n') { - y++; - continue; - } - - int cursorX = (int) boundingBox.x + x; - int cursorY = (int) boundingBox.y + y; - if(!Clay_PointIsInsideRect((Clay_Vector2) { .x = cursorX, .y = cursorY }, scissorBox)) { - continue; - } - - Console_MoveCursor(cursorX, cursorY); - printf("%c", text.chars[x]); - } - break; - } - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { - scissorBox = boundingBox; - break; - } - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { - scissorBox = fullWindow; - break; - } - case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { - Clay_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig; - Console_DrawRectangle( - (int)boundingBox.x, - (int)boundingBox.y, - (int)boundingBox.width, - (int)boundingBox.height, - config->color, - scissorBox); - break; - } - case CLAY_RENDER_COMMAND_TYPE_BORDER: { - Clay_BorderElementConfig *config = renderCommand->config.borderElementConfig; - // Left border - if (config->left.width > 0) { - Console_DrawRectangle( - (int)(boundingBox.x), - (int)(boundingBox.y + config->cornerRadius.topLeft), - (int)config->left.width, - (int)(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), - config->left.color, - scissorBox); - } - // Right border - if (config->right.width > 0) { - Console_DrawRectangle( - (int)(boundingBox.x + boundingBox.width - config->right.width), - (int)(boundingBox.y + config->cornerRadius.topRight), - (int)config->right.width, - (int)(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), - config->right.color, - scissorBox); - } - // Top border - if (config->top.width > 0) { - Console_DrawRectangle( - (int)(boundingBox.x + config->cornerRadius.topLeft), - (int)(boundingBox.y), - (int)(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), - (int)config->top.width, - config->top.color, - scissorBox); - } - // Bottom border - if (config->bottom.width > 0) { - Console_DrawRectangle( - (int)(boundingBox.x + config->cornerRadius.bottomLeft), - (int)(boundingBox.y + boundingBox.height - config->bottom.width), - (int)(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), - (int)config->bottom.width, - config->bottom.color, - scissorBox); - } - break; - } - default: { - printf("Error: unhandled render command."); -#ifdef CLAY_OVERFLOW_TRAP - raise(SIGTRAP); -#endif - exit(1); - } - } - } - - Console_MoveCursor(-1, -1); // TODO make the user not be able to write -} diff --git a/renderers/terminal/clay_renderer_terminal_ansi.c b/renderers/terminal/clay_renderer_terminal_ansi.c new file mode 100644 index 0000000..089bbe5 --- /dev/null +++ b/renderers/terminal/clay_renderer_terminal_ansi.c @@ -0,0 +1,195 @@ +#include "stdint.h" +#include "string.h" +#include "stdio.h" +#include "stdlib.h" +#include "math.h" + +#ifdef CLAY_OVERFLOW_TRAP +#include "signal.h" +#endif + +static inline void Console_MoveCursor(int x, int y) { + printf("\033[%d;%dH", y + 1, x + 1); +} + +bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { + // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want + // TODO to expose Clay__PointIsInsideRect + return point.x >= rect.x && point.x < rect.x + rect.width && point.y >= rect.y && point.y < rect.y + rect.height; +} + +static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, + Clay_BoundingBox scissorBox) { + float average = (color.r + color.g + color.b + color.a) / 4 / 255; + + for (int y = y0; y < height + y0; y++) { + for (int x = x0; x < width + x0; x++) { + if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = x, .y = y}, scissorBox)) { + continue; + } + + Console_MoveCursor(x, y); + // TODO there are only two colors actually drawn, the background and white + if (average > 0.75) { + printf("█"); + } else if (average > 0.5) { + printf("▓"); + } else if (average > 0.25) { + printf("▒"); + } else { + printf("░"); + } + } + } +} + +static inline Clay_Dimensions +Console_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { + Clay_Dimensions textSize = {0}; + int columnWidth = *(int *) userData; + + // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels + + float maxTextWidth = 0.0f; + float lineTextWidth = 0; + + float textHeight = 1; + + for (int i = 0; i < text.length; ++i) { + if (text.chars[i] == '\n') { + maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; + lineTextWidth = 0; + textHeight++; + continue; + } + lineTextWidth++; + } + + maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; + + textSize.width = maxTextWidth * columnWidth; + textSize.height = textHeight * columnWidth; + + return textSize; +} + +void Clay_Console_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth) { + printf("\033[H\033[J"); // Clear + + const Clay_BoundingBox fullWindow = { + .x = 0, + .y = 0, + .width = (float) width, + .height = (float) height, + }; + + Clay_BoundingBox scissorBox = fullWindow; + + for (int j = 0; j < renderCommands.length; j++) { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); + Clay_BoundingBox boundingBox = (Clay_BoundingBox) { + .x = roundf((renderCommand->boundingBox.x / columnWidth)), + .y = roundf((renderCommand->boundingBox.y / columnWidth)), + .width = roundf((renderCommand->boundingBox.width / columnWidth)), + .height = roundf((renderCommand->boundingBox.height / columnWidth)), + }; + switch (renderCommand->commandType) { + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData data = renderCommand->renderData.text; + Clay_StringSlice text = data.stringContents; + int y = 0; + for (int x = 0; x < text.length; x++) { + if (text.chars[x] == '\n') { + y++; + continue; + } + + int cursorX = (int) boundingBox.x + x; + int cursorY = (int) boundingBox.y + y; + if (cursorY > scissorBox.y + scissorBox.height) { + break; + } + if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = cursorX, .y = cursorY}, scissorBox)) { + continue; + } + + Console_MoveCursor(cursorX, cursorY); + printf("%c", text.chars[x]); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + scissorBox = boundingBox; + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + scissorBox = fullWindow; + break; + } + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData data = renderCommand->renderData.rectangle; + Console_DrawRectangle( + (int) boundingBox.x, + (int) boundingBox.y, + (int) boundingBox.width, + (int) boundingBox.height, + data.backgroundColor, + scissorBox); + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData data = renderCommand->renderData.border; + // Left border + if (data.width.left > 0) { + Console_DrawRectangle( + (int) (boundingBox.x), + (int) (boundingBox.y + data.cornerRadius.topLeft), + (int) data.width.left, + (int) (boundingBox.height - data.cornerRadius.topLeft - data.cornerRadius.bottomLeft), + data.color, + scissorBox); + } + // Right border + if (data.width.right > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + boundingBox.width - data.width.right), + (int) (boundingBox.y + data.cornerRadius.topRight), + (int) data.width.right, + (int) (boundingBox.height - data.cornerRadius.topRight - data.cornerRadius.bottomRight), + data.color, + scissorBox); + } + // Top border + if (data.width.top > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + data.cornerRadius.topLeft), + (int) (boundingBox.y), + (int) (boundingBox.width - data.cornerRadius.topLeft - data.cornerRadius.topRight), + (int) data.width.top, + data.color, + scissorBox); + } + // Bottom border + if (data.width.bottom > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + data.cornerRadius.bottomLeft), + (int) (boundingBox.y + boundingBox.height - data.width.bottom), + (int) (boundingBox.width - data.cornerRadius.bottomLeft - data.cornerRadius.bottomRight), + (int) data.width.bottom, + data.color, + scissorBox); + } + break; + } + default: { + printf("Error: unhandled render command."); +#ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); +#endif + exit(1); + } + } + } + + Console_MoveCursor(-1, -1); // TODO make the user not be able to write +}