diff --git a/CMakeLists.txt b/CMakeLists.txt index 10a49b8..02b20b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,12 +3,17 @@ project(clay) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +if(APPLE) + enable_language(OBJC) +endif() + add_subdirectory("examples/cpp-project-example") add_subdirectory("examples/raylib-multi-context") add_subdirectory("examples/raylib-sidebar-scrolling-container") # add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now if(NOT MSVC) add_subdirectory("examples/clay-official-website") + add_subdirectory("examples/SDL3-simple-demo") endif() add_subdirectory("examples/introducing-clay-video-demo") add_subdirectory("examples/SDL2-video-demo") diff --git a/examples/SDL3-simple-demo/CMakeLists.txt b/examples/SDL3-simple-demo/CMakeLists.txt new file mode 100644 index 0000000..844cfad --- /dev/null +++ b/examples/SDL3-simple-demo/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.27) + +# Project setup +project(clay_examples_sdl3_simple_demo C) +set(CMAKE_C_STANDARD 99) + +set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -Wall -Werror") +set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") + +include(FetchContent) +set(FETCHCONTENT_QUIET FALSE) + +# Download SDL3 +FetchContent_Declare( + SDL + GIT_REPOSITORY https://github.com/libsdl-org/SDL.git + GIT_TAG preview-3.1.6 + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) +message(STATUS "Using SDL via FetchContent") +FetchContent_MakeAvailable(SDL) +set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) + +# Download SDL_ttf +FetchContent_Declare( + SDL_ttf + GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git + GIT_TAG main # Slightly risky to use main branch, but it's the only one available + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE +) +message(STATUS "Using SDL_ttf via FetchContent") +FetchContent_MakeAvailable(SDL_ttf) +set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) + +# Example executable +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} PRIVATE + SDL3::SDL3 + SDL3_ttf::SDL3_ttf +) + +add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources +) diff --git a/examples/SDL3-simple-demo/main.c b/examples/SDL3-simple-demo/main.c new file mode 100644 index 0000000..e2d7fb6 --- /dev/null +++ b/examples/SDL3-simple-demo/main.c @@ -0,0 +1,185 @@ +#define SDL_MAIN_USE_CALLBACKS +#include +#include +#include + +#define CLAY_IMPLEMENTATION +#include "../../clay.h" + +#include + +#include "../../renderers/SDL3/clay_renderer_SDL3.c" + +static const Uint32 FONT_ID = 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}; + +typedef struct app_state { + SDL_Window *window; + SDL_Renderer *renderer; +} AppState; + +static inline Clay_Dimensions SDL_MeasureText(Clay_String *text, Clay_TextElementConfig *config) +{ + TTF_Font *font = gFonts[config->fontId]; + int width, height; + + if (!TTF_GetStringSize(font, text->chars, text->length, &width, &height)) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); + } + + return (Clay_Dimensions) { (float) width, (float) height }; +} + +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, + .fontSize = 24, + })); + } +} + +static Clay_RenderCommandArray Clay_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, + }, + .childGap = 10, + .padding = { 10, 10 }, + .layoutDirection = CLAY_TOP_TO_BOTTOM, + }), + CLAY_RECTANGLE({ + .color = COLOR_LIGHT, + }) + ) { + Label(CLAY_STRING("Button 1")); + Label(CLAY_STRING("Button 2")); + Label(CLAY_STRING("Button 3")); + } + return Clay_EndLayout(); +} + +void HandleClayErrors(Clay_ErrorData errorData) { + printf("%s", errorData.errorText.chars); +} + +SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + if (!TTF_Init()) { + return SDL_APP_FAILURE; + } + + AppState *state = SDL_calloc(1, sizeof(AppState)); + if (!state) { + return SDL_APP_FAILURE; + } + *appstate = state; + + if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->renderer)) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + SDL_SetWindowResizable(state->window, true); + + TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); + if (!font) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError()); + return SDL_APP_FAILURE; + } + + gFonts[FONT_ID] = font; + + /* Initialize Clay */ + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena clayMemory = (Clay_Arena) { + .memory = SDL_malloc(totalMemorySize), + .capacity = totalMemorySize + }; + + int width, height; + SDL_GetWindowSize(state->window, &width, &height); + Clay_SetMeasureTextFunction(SDL_MeasureText); + Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors }); + + *appstate = state; + return SDL_APP_CONTINUE; +} + +SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) +{ + SDL_AppResult ret_val = SDL_APP_CONTINUE; + + switch (event->type) { + case SDL_EVENT_QUIT: + ret_val = SDL_APP_SUCCESS; + break; + case SDL_EVENT_WINDOW_RESIZED: + Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 }); + break; + case SDL_EVENT_MOUSE_MOTION: + Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y }, + event->motion.state & SDL_BUTTON_LEFT); + break; + case SDL_EVENT_MOUSE_WHEEL: + Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->motion.xrel, event->motion.yrel }, 0.01f); + break; + default: + break; + }; + + return ret_val; +} + +SDL_AppResult SDL_AppIterate(void *appstate) +{ + AppState *state = appstate; + + Clay_RenderCommandArray render_commands = Clay_CreateLayout(); + + SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, 255); + SDL_RenderClear(state->renderer); + + SDL_RenderClayCommands(state->renderer, &render_commands); + + SDL_RenderPresent(state->renderer); + + return SDL_APP_CONTINUE; +} + +void SDL_AppQuit(void *appstate, SDL_AppResult result) +{ + (void) result; + + if (result != SDL_APP_SUCCESS) { + SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run"); + } + + AppState *state = appstate; + + if (state) { + if (state->renderer) + SDL_DestroyRenderer(state->renderer); + + if (state->window) + SDL_DestroyWindow(state->window); + + SDL_free(state); + } + TTF_Quit(); +} diff --git a/examples/SDL3-simple-demo/resources/Roboto-Regular.ttf b/examples/SDL3-simple-demo/resources/Roboto-Regular.ttf new file mode 100644 index 0000000..ddf4bfa Binary files /dev/null and b/examples/SDL3-simple-demo/resources/Roboto-Regular.ttf differ diff --git a/renderers/SDL3/README b/renderers/SDL3/README new file mode 100644 index 0000000..f960b09 --- /dev/null +++ b/renderers/SDL3/README @@ -0,0 +1,6 @@ +Please note, the SDL3 renderer is not 100% feature complete. It is currently missing: + +- Rounded rectangle corners +- Borders +- Images +- Scroll / Scissor handling diff --git a/renderers/SDL3/clay_renderer_SDL3.c b/renderers/SDL3/clay_renderer_SDL3.c new file mode 100644 index 0000000..aa052a5 --- /dev/null +++ b/renderers/SDL3/clay_renderer_SDL3.c @@ -0,0 +1,41 @@ +#include "../../clay.h" +#include +#include +#include + +/* This needs to be global because the "MeasureText" callback doesn't have a + * user data parameter */ +static TTF_Font *gFonts[1]; + +static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArray *rcommands) +{ + for (size_t i = 0; i < rcommands->length; i++) { + Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i); + Clay_BoundingBox bounding_box = rcmd->boundingBox; + const SDL_FRect rect = { bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height }; + + switch (rcmd->commandType) { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleElementConfig *config = rcmd->config.rectangleElementConfig; + Clay_Color color = config->color; + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer, &rect); + } break; + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextElementConfig *config = rcmd->config.textElementConfig; + Clay_String *text = &rcmd->text; + SDL_Color color = { config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a }; + + TTF_Font *font = gFonts[config->fontId]; + SDL_Surface *surface = TTF_RenderText_Blended(font, text->chars, text->length, color); + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + SDL_RenderTexture(renderer, texture, NULL, &rect); + + SDL_DestroySurface(surface); + SDL_DestroyTexture(texture); + } break; + default: + SDL_Log("Unknown render command type: %d", rcmd->commandType); + } + } +}