Compare commits

...

9 Commits

Author SHA1 Message Date
Avinal Kumar
4da2961d24
Merge 5c01540234 into 4612481d25 2025-01-26 15:37:19 +13:00
Nic Barker
4612481d25 [Documentation] Fix some typos in the README example code 2025-01-26 15:35:30 +13:00
Nic Barker
0a703de69a
[Core] Add z-index and string base to Render Commands (#227) 2025-01-26 15:28:35 +13:00
Nic Barker
cb62db77e3 [Documentation] Combine quick start steps into a single code block in main README 2025-01-26 15:22:46 +13:00
Cory
ea6109bd0b
[CMake] Make Examples Optional in CMAKE (#216) 2025-01-26 15:05:45 +13:00
arnauNau
c0dac38c87
[Renderers/SDL3] Add borders and rounded borders functionality. (#220) 2025-01-26 14:39:34 +13:00
Timothy Hoyt
c3fcf6ce12
[Bindings/C++] Link and information for ClayMan, a C++ wrapper library for clay (#218)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-01-24 20:51:00 +13:00
arnauNau
aba846a446
[Renderers/SDL3] Add rounded corners rectangle functionality (#219)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-01-23 09:30:24 +13:00
Avinal Kumar
5c01540234
[Examples/Website] Fix CMakeLists.txt to compile to wasm
- CMake was not compiling to WebAssembly
- This changes adds missing CMake directives for compiling to Wasm

Signed-off-by: Avinal Kumar <avinal.xlvii@gmail.com>
2025-01-04 19:02:02 +05:30
20 changed files with 384 additions and 125 deletions

View File

@ -3,17 +3,38 @@ project(clay)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON)
option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF)
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF)
option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF)
option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
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)
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE)
add_subdirectory("examples/cpp-project-example")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS)
if(NOT MSVC)
add_subdirectory("examples/clay-official-website")
endif()
add_subdirectory("examples/introducing-clay-video-demo")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES)
add_subdirectory("examples/raylib-multi-context")
add_subdirectory("examples/raylib-sidebar-scrolling-container")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES)
add_subdirectory("examples/SDL2-video-demo")
endif ()
if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES))
add_subdirectory("examples/SDL3-simple-demo")
endif()
add_subdirectory("examples/introducing-clay-video-demo")
add_subdirectory("examples/SDL2-video-demo")
# add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now

101
README.md
View File

@ -20,26 +20,17 @@ _An example GUI application built with clay_
## Quick Start
1. Download or clone clay.h and include it after defining `CLAY_IMPLEMENTATION` in one file.
Download or clone clay.h and include it after defining `CLAY_IMPLEMENTATION` in one file.
```C
// Must be defined in one file, _before_ #include "clay.h"
#define CLAY_IMPLEMENTATION
#include "clay.h"
```
#include "../../clay.h"
2. Ask clay for how much static memory it needs using [Clay_MinMemorySize()](#clay_minmemorysize), create an Arena for it to use with [Clay_CreateArenaWithCapacityAndMemory(size, void *memory)](#clay_createarenawithcapacityandmemory).
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};
```C
// Note: malloc is only used here as an example, any allocator that provides
// a pointer to addressable memory of at least totalMemorySize will work
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
```
3. Create an [ErrorHandler](#clay_errorhandler) for Clay to call when an internal error occurs, and initialize Clay with the Arena and handler by calling [Clay_Initialize(arena, dimensions, errorHandler)](#clay_initialize).
```C
void HandleClayErrors(Clay_ErrorData errorData) {
// See the Clay_ErrorData struct for more information
printf("%s", errorData.errorText.chars);
@ -48,51 +39,16 @@ void HandleClayErrors(Clay_ErrorData errorData) {
}
}
// In your startup function
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors });
```
4. Provide a `MeasureText(text, config)` function pointer with [Clay_SetMeasureTextFunction(function)](#clay_setmeasuretextfunction) so that clay can measure and wrap text.
```C
// Example measure text function
static inline Clay_Dimensions MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
static inline Clay_Dimensions MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) {
// Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
// Note: Clay_String->chars is not guaranteed to be null terminated
return (Clay_Dimensions) {
.width = text.length * config->fontSize, // <- this will only work for monospace fonts, see the renderers/ directory for more advanced text measurement
.height = config->fontSize
};
}
// Tell clay how to measure text
Clay_SetMeasureTextFunction(MeasureText);
```
4. **Optional** - Call [Clay_SetLayoutDimensions(dimensions)](#clay_setlayoutdimensions) if the window size of your application has changed.
```C
// Update internal layout dimensions
Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight });
```
5. **Optional** - Call [Clay_SetPointerState(pointerPosition, isPointerDown)](#clay_setpointerstate) if you want to use mouse interactions.
```C
// Update internal pointer position for handling mouseover / click / touch events
Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown);
```
6. **Optional** - Call [Clay_UpdateScrollContainers(enableDragScrolling, scrollDelta, deltaTime)](#clay_updatescrollcontainers) if you want to use clay's built in scrolling containers.
```C
// Update internal pointer position for handling mouseover / click / touch events
Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime);
```
7. Call [Clay_BeginLayout()](#clay_beginlayout) and declare your layout using the provided macros.
```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};
// Layout config is just a struct that can be declared statically, or inline
Clay_LayoutConfig sidebarItemLayout = (Clay_LayoutConfig) {
.sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) },
@ -103,8 +59,23 @@ void SidebarItemComponent() {
CLAY(CLAY_LAYOUT(sidebarItemLayout), CLAY_RECTANGLE({ .color = COLOR_ORANGE })) {}
}
// An example function to begin the "root" of your layout tree
Clay_RenderCommandArray CreateLayout() {
int main() {
// Note: malloc is only used here as an example, any allocator that provides
// a pointer to addressable memory of at least totalMemorySize will work
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors });
while(renderLoop()) { // Will be different for each renderer / environment
// Optional: Update internal layout dimensions to support resizing
Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight });
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling & debug tools
Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown);
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling and debug tools
Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime);
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
Clay_BeginLayout();
// An example of laying out a UI with a fixed width sidebar and flexible width main content
@ -113,7 +84,7 @@ Clay_RenderCommandArray CreateLayout() {
CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }),
CLAY_RECTANGLE({ .color = COLOR_LIGHT })
) {
CLAY(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16, .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("ProfilePicture"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {60, 60} })) {}
CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} }));
}
@ -122,20 +93,15 @@ Clay_RenderCommandArray CreateLayout() {
for (int i = 0; i < 5; i++) {
SidebarItemComponent();
}
}
CLAY(CLAY_ID("MainContent"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }}), CLAY_RECTANGLE({ .color = COLOR_LIGHT })) {}
}
// ...
});
```
8. Call [Clay_EndLayout()](#clay_endlayout) and process the resulting [Clay_RenderCommandArray](#clay_rendercommandarray) in your choice of renderer.
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
```C
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int i = 0; i < renderCommands.length; i++) {
// More comprehensive rendering examples can be found in the renderers/ directory
for (int i = 0; i < renderCommands.length; i++) {
Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i];
switch (renderCommand->commandType) {
@ -146,6 +112,9 @@ for (int i = 0; i < renderCommands.length; i++) {
}
// ... Implement handling of other command types
}
}
}
}
}
```

1
bindings/cpp/README.md Normal file
View File

@ -0,0 +1 @@
https://github.com/TimothyHoytBSME/ClayMan

View File

@ -184,7 +184,8 @@ ElementConfigUnion :: struct #raw_union {
RenderCommand :: struct {
boundingBox: BoundingBox,
config: ElementConfigUnion,
text: String,
text: StringSlice,
zIndex: i32,
id: u32,
commandType: RenderCommandType,
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

10
clay.h
View File

@ -471,7 +471,8 @@ CLAY__TYPEDEF(Clay_RenderCommandType, CLAY_PACKED_ENUM {
CLAY__TYPEDEF(Clay_RenderCommand, struct {
Clay_BoundingBox boundingBox;
Clay_ElementConfigUnion config;
Clay_String text; // TODO I wish there was a way to avoid having to have this on every render command
Clay_StringSlice text; // TODO I wish there was a way to avoid having to have this on every render command
int32_t zIndex;
uint32_t id;
Clay_RenderCommandType commandType;
});
@ -2600,6 +2601,7 @@ void Clay__CalculateFinalLayout() {
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) {
.boundingBox = clipHashMapItem->boundingBox,
.config = { .scrollElementConfig = Clay__StoreScrollElementConfig(CLAY__INIT(Clay_ScrollElementConfig)CLAY__DEFAULT_STRUCT) },
.zIndex = root->zIndex,
.id = Clay__RehashWithNumber(rootElement->id, 10), // TODO need a better strategy for managing derived ids
.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START,
});
@ -2732,7 +2734,8 @@ void Clay__CalculateFinalLayout() {
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) {
.boundingBox = { currentElementBoundingBox.x, currentElementBoundingBox.y + yPosition, wrappedLine.dimensions.width, wrappedLine.dimensions.height }, // TODO width
.config = configUnion,
.text = wrappedLine.line,
.text = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine.line.length, .chars = wrappedLine.line.chars, .baseChars = currentElement->childrenOrTextContent.textElementData->text.chars },
.zIndex = root->zIndex,
.id = Clay__HashNumber(lineIndex, currentElement->id).id,
.commandType = CLAY_RENDER_COMMAND_TYPE_TEXT,
});
@ -3945,7 +3948,8 @@ Clay_RenderCommandArray Clay_EndLayout() {
context->warningsEnabled = true;
}
if (context->booleanWarnings.maxElementsExceeded) {
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, .config = { .textElementConfig = &Clay__DebugView_ErrorTextConfig }, .text = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount"), .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT });
Clay_String message = CLAY_STRING("Clay Error: Layout elements exceeded Clay__maxElementCount");
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand ) { .boundingBox = { context->layoutDimensions.width / 2 - 59 * 4, context->layoutDimensions.height / 2, 0, 0 }, .config = { .textElementConfig = &Clay__DebugView_ErrorTextConfig }, .text = CLAY__INIT(Clay_StringSlice) { .length = message.length, .chars = message.chars, .baseChars = message.chars }, .commandType = CLAY_RENDER_COMMAND_TYPE_TEXT });
} else {
Clay__CalculateFinalLayout();
}

View File

@ -33,9 +33,31 @@ static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextEl
return (Clay_Dimensions) { (float) width, (float) height };
}
static void Label(Clay_String text)
static void Label(const Clay_String text, const int cornerRadius)
{
CLAY(CLAY_LAYOUT({ .padding = {16, 8} }), CLAY_RECTANGLE({ .color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE })) {
CLAY(CLAY_LAYOUT({ .padding = {8, 8} }),
CLAY_RECTANGLE({
.color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE,
.cornerRadius = cornerRadius,
})) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.textColor = { 255, 255, 255, 255 },
.fontId = FONT_ID,
.fontSize = 24,
}));
}
}
static void LabelBorder(const Clay_String text, const int cornerRadius, const int thickness)
{
CLAY(
CLAY_LAYOUT({
.padding = {16, 16, 8, 8 } }),
CLAY_BORDER_OUTSIDE_RADIUS(
thickness,
COLOR_BLUE,
cornerRadius)
){
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.textColor = { 255, 255, 255, 255 },
.fontId = FONT_ID,
@ -61,13 +83,21 @@ static Clay_RenderCommandArray Clay_CreateLayout()
.padding = { 10, 10 },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}),
CLAY_BORDER({
.left = { 20, COLOR_BLUE },
.right = { 20, COLOR_BLUE },
.bottom = { 20, COLOR_BLUE }
}),
CLAY_RECTANGLE({
.color = COLOR_LIGHT,
})
) {
Label(CLAY_STRING("Button 1"));
Label(CLAY_STRING("Button 2"));
Label(CLAY_STRING("Button 3"));
Label(CLAY_STRING("Rounded - Button 1"), 10);
Label(CLAY_STRING("Straight - Button 2") , 0);
Label(CLAY_STRING("Rounded+ - Button 3") , 20);
LabelBorder(CLAY_STRING("Border - Button 4"), 0, 5);
LabelBorder(CLAY_STRING("RoundedBorder - Button 5"), 10, 5);
LabelBorder(CLAY_STRING("RoundedBorder - Button 6"), 40, 15);
}
return Clay_EndLayout();
}

View File

@ -2,10 +2,32 @@ cmake_minimum_required(VERSION 3.27)
project(clay_official_website C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_COMPILER clang)
# Specify WebAssembly as target
set(CMAKE_C_FLAGS
"${CMAKE_C_FLAGS} -Wall -Werror -Os -DCLAY_WASM -mbulk-memory --target=wasm32 -nostdlib"
)
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -Wl,--strip-all,--export-dynamic,--no-entry,--export=__heap_base,--export=ACTIVE_RENDERER_INDEX,--initial-memory=6553600"
)
add_executable(clay_official_website main.c)
target_compile_options(clay_official_website PUBLIC -Wall -Werror -Wno-unknown-pragmas -Wno-error=missing-braces)
target_include_directories(clay_official_website PUBLIC .)
# Adjust WebAssembly output binary name
set_target_properties(clay_official_website PROPERTIES
OUTPUT_NAME index.wasm
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/clay
)
# Custom commands to copy additional resources
add_custom_command(TARGET clay_official_website POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/index.html ${CMAKE_BINARY_DIR}/clay/index.html
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/fonts ${CMAKE_BINARY_DIR}/clay/fonts
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/images ${CMAKE_BINARY_DIR}/clay/images
)
set(CMAKE_C_FLAGS_RELEASE "-O3")

View File

@ -96,6 +96,11 @@
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let stringSliceDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderDefinition = { type: 'struct', members: [
{name: 'width', type: 'uint32_t'},
{name: 'color', ...colorDefinition},
@ -155,7 +160,8 @@
{ name: 'height', type: 'float' },
]},
{ name: 'config', type: 'uint32_t'},
{ name: 'text', ...stringDefinition },
{ name: 'text', ...stringSliceDefinition },
{ name: 'zIndex', type: 'int32_t' },
{ name: 'id', type: 'uint32_t' },
{ name: 'commandType', type: 'uint32_t', },
]

View File

@ -96,6 +96,11 @@
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let stringSliceDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderDefinition = { type: 'struct', members: [
{name: 'width', type: 'uint32_t'},
{name: 'color', ...colorDefinition},
@ -155,7 +160,8 @@
{ name: 'height', type: 'float' },
]},
{ name: 'config', type: 'uint32_t'},
{ name: 'text', ...stringDefinition },
{ name: 'text', ...stringSliceDefinition },
{ name: 'zIndex', type: 'int32_t' },
{ name: 'id', type: 'uint32_t' },
{ name: 'commandType', type: 'uint32_t', },
]

View File

@ -27,7 +27,7 @@ target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC ra
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "/D CLAY_DEBUG")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Werror -DCLAY_DEBUG -fsanitize=address")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -DCLAY_DEBUG -fsanitize=address")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
endif()

View File

@ -58,7 +58,7 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextElementConfig *config = renderCommand->config.textElementConfig;
Clay_String text = renderCommand->text;
Clay_StringSlice text = renderCommand->text;
char *cloned = (char *)calloc(text.length + 1, 1);
memcpy(cloned, text.chars, text.length);
TTF_Font* font = fonts[config->fontId].font;

View File

@ -1,6 +1,4 @@
Please note, the SDL3 renderer is not 100% feature complete. It is currently missing:
- Rounded rectangle corners
- Borders
- Images
- Scroll / Scissor handling

View File

@ -6,25 +6,160 @@
/* This needs to be global because the "MeasureText" callback doesn't have a
* user data parameter */
static TTF_Font *gFonts[1];
/* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with
* no AA or low resolution might make it appear as jagged curves) */
static int NUM_CIRCLE_SEGMENTS = 16;
//all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles.
static void SDL_RenderFillRoundedRect(SDL_Renderer *renderer, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) {
const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 };
int indexCount = 0, vertexCount = 0;
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
const float clampedRadius = SDL_min(cornerRadius, minRadius);
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int) clampedRadius * 0.5f);
int totalVertices = 4 + (4 * (numCircleSegments * 2)) + 2*4;
int totalIndices = 6 + (4 * (numCircleSegments * 3)) + 6*4;
SDL_Vertex vertices[totalVertices];
int indices[totalIndices];
//define center rectangle
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL
indices[indexCount++] = 0;
indices[indexCount++] = 1;
indices[indexCount++] = 3;
indices[indexCount++] = 1;
indices[indexCount++] = 2;
indices[indexCount++] = 3;
//define rounded corners as triangle fans
const float step = (SDL_PI_F/2) / numCircleSegments;
for (int i = 0; i < numCircleSegments; i++) {
const float angle1 = (float)i * step;
const float angle2 = ((float)i + 1.0f) * step;
for (int j = 0; j < 4; j++) { // Iterate over four corners
float cx, cy, signX, signY;
switch (j) {
case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left
case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right
case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right
case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left
default: return;
}
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} };
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} };
indices[indexCount++] = j; // Connect to corresponding central rectangle vertex
indices[indexCount++] = vertexCount - 2;
indices[indexCount++] = vertexCount - 1;
}
}
//Define edge rectangles
// Top edge
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR
indices[indexCount++] = 0;
indices[indexCount++] = vertexCount - 2; //TL
indices[indexCount++] = vertexCount - 1; //TR
indices[indexCount++] = 1;
indices[indexCount++] = 0;
indices[indexCount++] = vertexCount - 1; //TR
// Right edge
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB
indices[indexCount++] = 1;
indices[indexCount++] = vertexCount - 2; //RT
indices[indexCount++] = vertexCount - 1; //RB
indices[indexCount++] = 2;
indices[indexCount++] = 1;
indices[indexCount++] = vertexCount - 1; //RB
// Bottom edge
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL
indices[indexCount++] = 2;
indices[indexCount++] = vertexCount - 2; //BR
indices[indexCount++] = vertexCount - 1; //BL
indices[indexCount++] = 3;
indices[indexCount++] = 2;
indices[indexCount++] = vertexCount - 1; //BL
// Left edge
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT
indices[indexCount++] = 3;
indices[indexCount++] = vertexCount - 2; //LB
indices[indexCount++] = vertexCount - 1; //LT
indices[indexCount++] = 0;
indices[indexCount++] = 3;
indices[indexCount++] = vertexCount - 1; //LT
// Render everything
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
}
static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
const float radStart = startAngle * (SDL_PI_F / 180.0f);
const float radEnd = endAngle * (SDL_PI_F / 180.0f);
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)(radius * 1.5f)); //increase circle segments for larger circles, 1.5 is arbitrary.
const float angleStep = (radEnd - radStart) / (float)numCircleSegments;
const float thicknessStep = 0.4f; //arbitrary value to avoid overlapping lines. Changing THICKNESS_STEP or numCircleSegments might cause artifacts.
for (float t = thicknessStep; t < thickness - thicknessStep; t += thicknessStep) {
SDL_FPoint points[numCircleSegments + 1];
const float clampedRadius = SDL_max(radius - t, 1.0f);
for (int i = 0; i <= numCircleSegments; i++) {
const float angle = radStart + i * angleStep;
points[i] = (SDL_FPoint){
SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius),
SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) };
}
SDL_RenderLines(renderer, points, numCircleSegments + 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 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;
const Clay_RectangleElementConfig *config = rcmd->config.rectangleElementConfig;
const Clay_Color color = config->color;
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
if (config->cornerRadius.topLeft > 0) {
SDL_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, color);
} else {
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 };
const Clay_TextElementConfig *config = rcmd->config.textElementConfig;
const Clay_StringSlice *text = &rcmd->text;
const 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);
@ -34,6 +169,72 @@ static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArr
SDL_DestroySurface(surface);
SDL_DestroyTexture(texture);
} break;
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
const Clay_BorderElementConfig *config = rcmd->config.borderElementConfig;
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
const Clay_CornerRadius clampedRadii = {
.topLeft = SDL_min(config->cornerRadius.topLeft, minRadius),
.topRight = SDL_min(config->cornerRadius.topRight, minRadius),
.bottomLeft = SDL_min(config->cornerRadius.bottomLeft, minRadius),
.bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius)
};
//edges
SDL_SetRenderDrawColor(renderer, config->left.color.r, config->left.color.g, config->left.color.b, config->left.color.a);
if (config->left.width > 0) {
const float starting_y = rect.y + clampedRadii.topLeft;
const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft;
SDL_FRect line = { rect.x, starting_y, config->left.width, length };
SDL_RenderFillRect(renderer, &line);
}
if (config->right.width > 0) {
const float starting_x = rect.x + rect.w - (float)config->right.width;
const float starting_y = rect.y + clampedRadii.topRight;
const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, config->right.width, length };
SDL_RenderFillRect(renderer, &line);
}
if (config->top.width > 0) {
const float starting_x = rect.x + clampedRadii.topLeft;
const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight;
SDL_FRect line = { starting_x, rect.y, length, config->top.width };
SDL_RenderFillRect(renderer, &line);
}
if (config->bottom.width > 0) {
const float starting_x = rect.x + clampedRadii.bottomLeft;
const float starting_y = rect.y + rect.h - (float)config->bottom.width;
const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, length, config->bottom.width };
SDL_SetRenderDrawColor(renderer, config->bottom.color.r, config->bottom.color.g, config->bottom.color.b, config->bottom.color.a);
SDL_RenderFillRect(renderer, &line);
}
//corners
if (config->cornerRadius.topLeft > 0) {
const float centerX = rect.x + clampedRadii.topLeft -1;
const float centerY = rect.y + clampedRadii.topLeft;
SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
180.0f, 270.0f, config->top.width, config->top.color);
}
if (config->cornerRadius.topRight > 0) {
const float centerX = rect.x + rect.w - clampedRadii.topRight -1;
const float centerY = rect.y + clampedRadii.topRight;
SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
270.0f, 360.0f, config->top.width, config->top.color);
}
if (config->cornerRadius.bottomLeft > 0) {
const float centerX = rect.x + clampedRadii.bottomLeft -1;
const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1;
SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
90.0f, 180.0f, config->bottom.width, config->bottom.color);
}
if (config->cornerRadius.bottomRight > 0) {
const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations???
const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1;
SDL_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
0.0f, 90.0f, config->bottom.width, config->bottom.color);
}
} break;
default:
SDL_Log("Unknown render command type: %d", rcmd->commandType);
}

View File

@ -138,7 +138,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
{
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
Clay_String text = renderCommand->text;
Clay_StringSlice text = renderCommand->text;
char *cloned = (char *)malloc(text.length + 1);
memcpy(cloned, text.chars, text.length);
cloned[text.length] = '\0';