From 7ce74ba46c01f32e4517032e9da76bf54ecf7a43 Mon Sep 17 00:00:00 2001 From: EmmanuelMess Date: Mon, 23 Dec 2024 01:23:29 -0300 Subject: [PATCH 1/2] add initial implementation of terminal renderer --- CMakeLists.txt | 1 + examples/terminal-example/CMakeLists.txt | 12 ++ examples/terminal-example/main.c | 54 ++++++++ renderers/terminal/clay_renderer_terminal.c | 130 ++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100644 examples/terminal-example/CMakeLists.txt create mode 100644 examples/terminal-example/main.c create mode 100644 renderers/terminal/clay_renderer_terminal.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0d72e..15553df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,4 +8,5 @@ if(NOT MSVC) add_subdirectory("examples/raylib-sidebar-scrolling-container") add_subdirectory("examples/cairo-pdf-rendering") add_subdirectory("examples/clay-official-website") + add_subdirectory("examples/terminal-example") endif() diff --git a/examples/terminal-example/CMakeLists.txt b/examples/terminal-example/CMakeLists.txt new file mode 100644 index 0000000..67b5103 --- /dev/null +++ b/examples/terminal-example/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.27) +project(clay_examples_terminal C) +set(CMAKE_C_STANDARD 99) + +add_executable(clay_examples_terminal main.c) + +target_compile_options(clay_examples_terminal PUBLIC) +target_include_directories(clay_examples_terminal PUBLIC .) + +target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY}) +set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") \ No newline at end of file diff --git a/examples/terminal-example/main.c b/examples/terminal-example/main.c new file mode 100644 index 0000000..b36330e --- /dev/null +++ b/examples/terminal-example/main.c @@ -0,0 +1,54 @@ +// Must be defined in one file, _before_ #include "clay.h" +#define CLAY_IMPLEMENTATION + +#include +#include "../../clay.h" +#include "../../renderers/terminal/clay_renderer_terminal.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(); +} + +int main() { + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + Clay_Initialize(arena, (Clay_Dimensions) { .width = 80, .height = 24 }); // 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); + + while(true) { + Clay_RenderCommandArray layout = CreateLayout(); + + Clay_Console_Render(layout); + + 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 new file mode 100644 index 0000000..6ca385b --- /dev/null +++ b/renderers/terminal/clay_renderer_terminal.c @@ -0,0 +1,130 @@ +#include "stdint.h" +#include "string.h" +#include "stdio.h" +#include "stdlib.h" +#include "math.h" +#ifdef CLAY_OVERFLOW_TRAP +#include "signal.h" +#endif + +#define gotoxy(x, y) printf("\033[%d;%dH", y+1, x+1) + +static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color) { + 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++) { + gotoxy(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) +{ + printf("\033[H\033[J"); // Clear + + 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 k = 0; + for (int i = 0; i < text.length; i++) { + if(text.chars[i] == '\n') { + k++; + continue; + } + + gotoxy((int) boundingBox.x + i, (int) boundingBox.y + k); + printf("%c", text.chars[i]); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + //TODO + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + //TODO + 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); + 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); + } + // 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); + } + // 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); + } + // 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); + } + break; + } + default: { + printf("Error: unhandled render command."); +#ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); +#endif + exit(1); + } + } + } +} From b0fe41186c9ee3aeca72009d754c2fc763b16109 Mon Sep 17 00:00:00 2001 From: EmmanuelMess Date: Thu, 26 Dec 2024 15:52:21 -0300 Subject: [PATCH 2/2] fix naming conventions, add scissoring --- examples/terminal-example/main.c | 7 +- renderers/terminal/clay_renderer_terminal.c | 94 +++++++++++++++++---- 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/examples/terminal-example/main.c b/examples/terminal-example/main.c index b36330e..edbf349 100644 --- a/examples/terminal-example/main.c +++ b/examples/terminal-example/main.c @@ -37,16 +37,19 @@ Clay_RenderCommandArray CreateLayout() { } int main() { + const int width = 80; + const int height = 24; + uint64_t totalMemorySize = Clay_MinMemorySize(); Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); - Clay_Initialize(arena, (Clay_Dimensions) { .width = 80, .height = 24 }); // TODO this is wrong, but I have no idea what the actual size of the terminal is in pixels + 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); while(true) { Clay_RenderCommandArray layout = CreateLayout(); - Clay_Console_Render(layout); + Clay_Console_Render(layout, width, height); fflush(stdout); sleep(1); diff --git a/renderers/terminal/clay_renderer_terminal.c b/renderers/terminal/clay_renderer_terminal.c index 6ca385b..fb8a650 100644 --- a/renderers/terminal/clay_renderer_terminal.c +++ b/renderers/terminal/clay_renderer_terminal.c @@ -7,9 +7,18 @@ #include "signal.h" #endif -#define gotoxy(x, y) printf("\033[%d;%dH", y+1, x+1) +static inline void Console_MoveCursor(int x, int y) { + printf("\033[%d;%dH", y+1, x+1); +} -static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color) { +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; @@ -17,7 +26,11 @@ static inline void Console_DrawRectangle(int x0, int y0, int width, int height, for (int y = y0; y < height; y++) { for (int x = x0; x < width; x++) { - gotoxy(x, y); + 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(" "); @@ -61,10 +74,19 @@ void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned i //TODO } -void Clay_Console_Render(Clay_RenderCommandArray renderCommands) +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); @@ -73,48 +95,84 @@ void Clay_Console_Render(Clay_RenderCommandArray renderCommands) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_String text = renderCommand->text; - int k = 0; - for (int i = 0; i < text.length; i++) { - if(text.chars[i] == '\n') { - k++; + int y = 0; + for (int x = 0; x < text.length; x++) { + if(text.chars[x] == '\n') { + y++; continue; } - gotoxy((int) boundingBox.x + i, (int) boundingBox.y + k); - printf("%c", text.chars[i]); + 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: { - //TODO + scissorBox = boundingBox; break; } case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { - //TODO + 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); + 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); + 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); + 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); + 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); + 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; } @@ -127,4 +185,6 @@ void Clay_Console_Render(Clay_RenderCommandArray renderCommands) } } } + + Console_MoveCursor(-1, -1); // TODO make the user not be able to write }