diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f0d72e..4d773ad 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/minimal-sdl2") endif() diff --git a/examples/minimal-sdl2/CMakeLists.txt b/examples/minimal-sdl2/CMakeLists.txt new file mode 100644 index 0000000..2f9260c --- /dev/null +++ b/examples/minimal-sdl2/CMakeLists.txt @@ -0,0 +1,43 @@ +cmake_minimum_required(VERSION 3.27) +project(minimal_sdl2 C) +set(CMAKE_C_STANDARD 99) + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) + +FetchContent_Declare( + SDL2 + GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" + GIT_TAG "release-2.30.10" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(SDL2) + +FetchContent_Declare( + SDL2_ttf + GIT_REPOSITORY "https://github.com/libsdl-org/SDL_ttf.git" + GIT_TAG "release-2.22.0" + GIT_PROGRESS TRUE + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(SDL2_ttf) + +add_executable(minimal_sdl2 main.c) + +target_compile_options(minimal_sdl2 PUBLIC) +target_include_directories(minimal_sdl2 PUBLIC .) + +target_link_libraries(minimal_sdl2 PUBLIC + SDL2::SDL2main + SDL2::SDL2-static + SDL2_ttf::SDL2_ttf-static +) +set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") + +add_custom_command( + TARGET minimal_sdl2 POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources) diff --git a/examples/minimal-sdl2/main.c b/examples/minimal-sdl2/main.c new file mode 100644 index 0000000..fc0cc39 --- /dev/null +++ b/examples/minimal-sdl2/main.c @@ -0,0 +1,220 @@ +#define CLAY_IMPLEMENTATION +#include "../../clay.h" + +#include +#include + +#include +#include +#include +#include + + +static const uint32_t FONT_ID_BODY_24 = 0; +static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; +static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; +static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; + + +static void Label(Clay_String text) { + CLAY(CLAY_LAYOUT({ .padding = {16, 8} }), + CLAY_RECTANGLE({ .color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE }) + ) { + CLAY_TEXT(text, CLAY_TEXT_CONFIG({ + .textColor = { 255, 255, 255, 255 }, + .fontId = FONT_ID_BODY_24, + .fontSize = 24, + })); + } +} + + +static Clay_RenderCommandArray CreateLayout() { + Clay_BeginLayout(); + CLAY(CLAY_ID("MainContent"), + CLAY_LAYOUT({ + .sizing = { + .width = CLAY_SIZING_GROW(), + .height = CLAY_SIZING_GROW(), + }, + .childAlignment = { + .x = CLAY_ALIGN_X_CENTER, + .y = CLAY_ALIGN_Y_CENTER, + } + }), + CLAY_RECTANGLE({ + .color = COLOR_LIGHT, + }) + ) { + Label(CLAY_STRING("Hello, World!")); + } + return Clay_EndLayout(); +} + + +typedef struct +{ + uint32_t fontId; + TTF_Font *font; +} Font; +static Font fonts[1]; + + +static Clay_Dimensions MeasureText(Clay_String *text, Clay_TextElementConfig *config); +static void Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands); + + +int main(void) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError()); + return 1; + } + if (TTF_Init() < 0) { + fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError()); + return 1; + } + + TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); + if (!font) { + fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError()); + return 1; + } + fonts[FONT_ID_BODY_24] = (Font) { + .fontId = FONT_ID_BODY_24, + .font = font, + }; + + SDL_Window *window = NULL; + SDL_Renderer *renderer = NULL; + if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) { + fprintf(stderr, "Error: could not create window and renderer: %s", SDL_GetError()); + } + + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena clayMemory = (Clay_Arena) { + .label = CLAY_STRING("Clay Memory Arena"), + .capacity = totalMemorySize, + .memory = (char *)malloc(totalMemorySize), + }; + + Clay_SetMeasureTextFunction(MeasureText); + + int windowWidth = 0; + int windowHeight = 0; + SDL_GetWindowSize(window, &windowWidth, &windowHeight); + Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); + + while (true) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: goto quit; + } + } + int mouseX = 0; + int mouseY = 0; + Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY); + Clay_Vector2 mousePosition = (Clay_Vector2){ (float)mouseX, (float)mouseY }; + Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1)); + + SDL_GetWindowSize(window, &windowWidth, &windowHeight); + Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); + + Clay_RenderCommandArray renderCommands = CreateLayout(); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + Render(renderer, renderCommands); + + SDL_RenderPresent(renderer); + } +quit: + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + TTF_Quit(); + SDL_Quit(); + return 0; +} + + +static Clay_Dimensions MeasureText(Clay_String *text, Clay_TextElementConfig *config) +{ + TTF_Font *font = fonts[config->fontId].font; + char *chars = (char *)calloc(text->length + 1, 1); + memcpy(chars, text->chars, text->length); + int width = 0; + int height = 0; + if (TTF_SizeUTF8(font, chars, &width, &height) < 0) { + fprintf(stderr, "Error: could not measure text: %s\n", TTF_GetError()); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + exit(1); + } + free(chars); + return (Clay_Dimensions) { + .width = (float)width, + .height = (float)height, + }; +} + + +static void Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands) +{ + for (uint32_t i = 0; i < renderCommands.length; i++) + { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); + Clay_BoundingBox boundingBox = renderCommand->boundingBox; + switch (renderCommand->commandType) + { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig; + Clay_Color color = config->color; + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_FRect rect = (SDL_FRect) { + .x = boundingBox.x, + .y = boundingBox.y, + .w = boundingBox.width, + .h = boundingBox.height, + }; + SDL_RenderFillRectF(renderer, &rect); + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextElementConfig *config = renderCommand->config.textElementConfig; + Clay_String text = renderCommand->text; + char *cloned = (char *)calloc(text.length + 1, 1); + memcpy(cloned, text.chars, text.length); + TTF_Font* font = fonts[config->fontId].font; + SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) { + .r = (Uint8)config->textColor.r, + .g = (Uint8)config->textColor.g, + .b = (Uint8)config->textColor.b, + .a = (Uint8)config->textColor.a, + }); + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + + SDL_Rect destination = (SDL_Rect){ + .x = (Uint8)boundingBox.x, + .y = (Uint8)boundingBox.y, + .w = (Uint8)boundingBox.width, + .h = (Uint8)boundingBox.height, + }; + SDL_RenderCopy(renderer, texture, NULL, &destination); + + SDL_DestroyTexture(texture); + SDL_FreeSurface(surface); + free(cloned); + break; + } + default: { + fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType); + #ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); + #endif + exit(1); + } + } + } +} diff --git a/examples/minimal-sdl2/resources/Roboto-Regular.ttf b/examples/minimal-sdl2/resources/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/examples/minimal-sdl2/resources/Roboto-Regular.ttf differ