Skip to content

Commit

Permalink
[Renderers/SDL3] Adds an example using SDL3 as a renderer (#107)
Browse files Browse the repository at this point in the history
Co-authored-by: Nic Barker <[email protected]>
  • Loading branch information
LiquidityC and nicbarker authored Jan 19, 2025
1 parent 9d3fba3 commit 8e7e30d
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
49 changes: 49 additions & 0 deletions examples/SDL3-simple-demo/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
185 changes: 185 additions & 0 deletions examples/SDL3-simple-demo/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>

#define CLAY_IMPLEMENTATION
#include "../../clay.h"

#include <stdio.h>

#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();
}
Binary file not shown.
6 changes: 6 additions & 0 deletions renderers/SDL3/README
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions renderers/SDL3/clay_renderer_SDL3.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#include "../../clay.h"
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>

/* 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);
}
}
}

0 comments on commit 8e7e30d

Please sign in to comment.