mirror of
https://github.com/nicbarker/clay.git
synced 2025-05-12 21:38:06 +00:00
Compare commits
15 Commits
eea578ecea
...
9665efda42
Author | SHA1 | Date | |
---|---|---|---|
|
9665efda42 | ||
|
5fae7a6249 | ||
|
1bcf256e4d | ||
|
e9f2e6c4f1 | ||
|
34f2dab9e8 | ||
|
951d785deb | ||
|
4612481d25 | ||
|
0a703de69a | ||
|
cb62db77e3 | ||
|
ea6109bd0b | ||
|
c0dac38c87 | ||
|
c3fcf6ce12 | ||
|
aba846a446 | ||
|
63a74a92a8 | ||
|
8d3cadc52e |
@ -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)
|
||||
add_subdirectory("examples/clay-official-website")
|
||||
add_subdirectory("examples/SDL3-simple-demo")
|
||||
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE)
|
||||
add_subdirectory("examples/cpp-project-example")
|
||||
endif()
|
||||
add_subdirectory("examples/introducing-clay-video-demo")
|
||||
add_subdirectory("examples/SDL2-video-demo")
|
||||
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/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
|
||||
|
155
README.md
155
README.md
@ -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,48 +59,61 @@ 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() {
|
||||
Clay_BeginLayout();
|
||||
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));
|
||||
|
||||
// An example of laying out a UI with a fixed width sidebar and flexible width main content
|
||||
CLAY(CLAY_ID("OuterContainer"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }), CLAY_RECTANGLE({ .color = {250,250,255,255} })) {
|
||||
CLAY(CLAY_ID("SideBar"),
|
||||
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("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} }));
|
||||
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
|
||||
CLAY(CLAY_ID("OuterContainer"), CLAY_LAYOUT({ .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }), CLAY_RECTANGLE({ .color = {250,250,255,255} })) {
|
||||
CLAY(CLAY_ID("SideBar"),
|
||||
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("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} }));
|
||||
}
|
||||
|
||||
// Standard C code like loops etc work inside components
|
||||
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 })) {}
|
||||
}
|
||||
|
||||
// Standard C code like loops etc work inside components
|
||||
for (int i = 0; i < 5; i++) {
|
||||
SidebarItemComponent();
|
||||
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
|
||||
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
|
||||
|
||||
// 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) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
DrawRectangle(
|
||||
renderCommand->boundingBox,
|
||||
renderCommand->config.rectangleElementConfig->color);
|
||||
}
|
||||
// ... Implement handling of other command types
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
```C
|
||||
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
|
||||
|
||||
for (int i = 0; i < renderCommands.length; i++) {
|
||||
Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i];
|
||||
|
||||
switch (renderCommand->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
DrawRectangle(
|
||||
renderCommand->boundingBox,
|
||||
renderCommand->config.rectangleElementConfig->color);
|
||||
}
|
||||
// ... Implement handling of other command types
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -819,7 +788,7 @@ if (buttonIsHovered && leftMouseButtonPressed) {
|
||||
|
||||
### CLAY_IDI()
|
||||
|
||||
`Clay_ElementId CLAY_IDI(char *label, int index)`
|
||||
`Clay_ElementId CLAY_IDI(char *label, int32_t index)`
|
||||
|
||||
An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
|
||||
@ -866,7 +835,7 @@ for (int i = 0; i < headerButtons.length; i++) {
|
||||
|
||||
### CLAY_IDI_LOCAL()
|
||||
|
||||
`Clay_ElementId CLAY_IDI_LOCAL(char *label, int index)`
|
||||
`Clay_ElementId CLAY_IDI_LOCAL(char *label, int32_t index)`
|
||||
|
||||
An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
|
||||
|
1
bindings/cpp/README.md
Normal file
1
bindings/cpp/README.md
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/TimothyHoytBSME/ClayMan
|
@ -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.
Binary file not shown.
Binary file not shown.
@ -33,9 +33,13 @@ 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,
|
||||
@ -44,6 +48,24 @@ static void Label(Clay_String text)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
.fontSize = 24,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
static Clay_RenderCommandArray Clay_CreateLayout()
|
||||
{
|
||||
Clay_BeginLayout();
|
||||
@ -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();
|
||||
}
|
||||
|
@ -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', },
|
||||
]
|
||||
|
Binary file not shown.
@ -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', },
|
||||
]
|
||||
|
@ -13,7 +13,9 @@ int main(void) {
|
||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize));
|
||||
Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors });
|
||||
Clay_BeginLayout();
|
||||
CLAY(CLAY_RECTANGLE({ .color = {255,255,255,0} }), CLAY_LAYOUT(layoutElement)) {}
|
||||
CLAY(CLAY_RECTANGLE({ .color = {255,255,255,0} }), CLAY_LAYOUT(layoutElement)) {
|
||||
CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
|
||||
}
|
||||
Clay_EndLayout();
|
||||
return 0;
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ target_include_directories(clay_examples_raylib_multi_context PUBLIC .)
|
||||
|
||||
target_link_libraries(clay_examples_raylib_multi_context PUBLIC raylib)
|
||||
|
||||
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
|
||||
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3")
|
||||
|
||||
add_custom_command(
|
||||
|
@ -10,7 +10,7 @@ void RenderHeaderButton(Clay_String text) {
|
||||
CLAY_LAYOUT({ .padding = { 16, 16, 8, 8 }}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 140, 140, 140, 255 },
|
||||
.cornerRadius = 5
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(5)
|
||||
})
|
||||
) {
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
|
||||
@ -79,7 +79,7 @@ typedef struct {
|
||||
intptr_t memory;
|
||||
} Arena;
|
||||
|
||||
Arena frameArena = {};
|
||||
Arena frameArena = {0};
|
||||
|
||||
Clay_RenderCommandArray CreateLayout(Clay_Context* context, float yOffset, int32_t* documentIndex) {
|
||||
Clay_SetCurrentContext(context);
|
||||
@ -104,13 +104,13 @@ Clay_RenderCommandArray CreateLayout(Clay_Context* context, float yOffset, int32
|
||||
);
|
||||
|
||||
Clay_Sizing layoutExpand = {
|
||||
.width = CLAY_SIZING_GROW(),
|
||||
.height = CLAY_SIZING_GROW()
|
||||
.width = CLAY_SIZING_GROW(0),
|
||||
.height = CLAY_SIZING_GROW(0)
|
||||
};
|
||||
|
||||
Clay_RectangleElementConfig contentBackgroundConfig = {
|
||||
.color = { 90, 90, 90, 255 },
|
||||
.cornerRadius = 8
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
};
|
||||
|
||||
Clay_BeginLayout();
|
||||
@ -138,14 +138,14 @@ Clay_RenderCommandArray CreateLayout(Clay_Context* context, float yOffset, int32
|
||||
.childGap = 8,
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(250),
|
||||
.height = CLAY_SIZING_GROW()
|
||||
.height = CLAY_SIZING_GROW(0)
|
||||
}
|
||||
})
|
||||
) {
|
||||
for (int i = 0; i < documents.length; i++) {
|
||||
Document document = documents.documents[i];
|
||||
Clay_LayoutConfig sidebarButtonLayout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW() },
|
||||
.sizing = { .width = CLAY_SIZING_GROW(0) },
|
||||
.padding = CLAY_PADDING_ALL(16)
|
||||
};
|
||||
|
||||
@ -154,7 +154,7 @@ Clay_RenderCommandArray CreateLayout(Clay_Context* context, float yOffset, int32
|
||||
CLAY_LAYOUT(sidebarButtonLayout),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 120, 120, 120, 255 },
|
||||
.cornerRadius = 8,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8),
|
||||
})
|
||||
) {
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
@ -173,9 +173,9 @@ Clay_RenderCommandArray CreateLayout(Clay_Context* context, float yOffset, int32
|
||||
Clay_Hovered()
|
||||
? CLAY_RECTANGLE({
|
||||
.color = { 120, 120, 120, 120 },
|
||||
.cornerRadius = 8
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
})
|
||||
: 0
|
||||
: (void)0
|
||||
) {
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
|
@ -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}")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
endif()
|
||||
|
||||
|
24
fuzz/fuzzing_target.c
Normal file
24
fuzz/fuzzing_target.c
Normal file
@ -0,0 +1,24 @@
|
||||
#include "clay.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
||||
if (size < sizeof(Clay_String)) return 0;
|
||||
|
||||
Clay_String testString = { .length = size, .chars = (const char *)data };
|
||||
|
||||
Clay_Dimensions dimensions = MeasureText(&testString, NULL);
|
||||
|
||||
// Call other critical functions
|
||||
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(1024, (void*)data);
|
||||
Clay_Initialize(arena, (Clay_Dimensions){1024, 768});
|
||||
Clay_SetPointerState((Clay_Vector2){0, 0}, false);
|
||||
Clay_BeginLayout();
|
||||
Clay_EndLayout();
|
||||
|
||||
// Handle pointer state changes
|
||||
Clay_SetPointerState((Clay_Vector2){1, 1}, true);
|
||||
Clay_SetPointerState((Clay_Vector2){2, 2}, false);
|
||||
|
||||
return 0;
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
$TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) {
|
||||
if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) {
|
||||
array->internalArray[array->length++] = item;
|
||||
return &array->internalArray[array->length - 1];
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
void $NAME$_Add($NAME$ *array, $TYPE$ item) {
|
||||
if (Clay__Array_AddCapacityCheck(array->length, array->capacity)) {
|
||||
array->internalArray[array->length++] = item;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
|
||||
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__ALIGNMENT($TYPE$), arena)};
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
|
||||
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__POINTER_ALIGNMENT, arena)};
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
CLAY__TYPEDEF($NAME$, struct
|
||||
{
|
||||
int32_t capacity;
|
||||
int32_t length;
|
||||
$TYPE$ *internalArray;
|
||||
});
|
@ -1,5 +0,0 @@
|
||||
CLAY__TYPEDEF($NAME$Slice, struct
|
||||
{
|
||||
int32_t length;
|
||||
$TYPE$ *internalArray;
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
$TYPE$ *$NAME$_Get($NAME$ *array, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$TYPE$ $NAME$_Get($NAME$ *array, int32_t index) {
|
||||
return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int32_t index) {
|
||||
if (Clay__Array_RangeCheck(index, array->length)) {
|
||||
array->length--;
|
||||
$TYPE$ removed = array->internalArray[index];
|
||||
array->internalArray[index] = array->internalArray[array->length];
|
||||
return removed;
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
void $NAME$_Set($NAME$ *array, int32_t index, $TYPE$ value) {
|
||||
if (Clay__Array_RangeCheck(index, array->capacity)) {
|
||||
array->internalArray[index] = value;
|
||||
array->length = index < array->length ? array->length : index + 1;
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let files = ['../clay.h'];
|
||||
|
||||
let templates = ['./'];
|
||||
function readCTemplatesRecursive(directory) {
|
||||
fs.readdirSync(directory).forEach(template => {
|
||||
const absolute = path.join(directory, template);
|
||||
if (fs.statSync(absolute).isDirectory()) return readCTemplatesRecursive(absolute);
|
||||
else if (template.endsWith('template.c')) {
|
||||
return templates.push(absolute);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readCTemplatesRecursive(__dirname);
|
||||
|
||||
for (const file of files) {
|
||||
const contents = fs.readFileSync(file, 'utf8');
|
||||
const lines = contents.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith('// __GENERATED__ template')) {
|
||||
const [comment, generated, templateOpen, templateNames, ...args] = line.split(" ");
|
||||
let matchingEndingLine = -1;
|
||||
for (let j = i + 1; j < lines.length; j++) {
|
||||
if (lines[j].startsWith('// __GENERATED__ template')) {
|
||||
matchingEndingLine = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingEndingLine !== -1) {
|
||||
i++;
|
||||
lines.splice(i, matchingEndingLine - (i));
|
||||
lines.splice(i, 0, ['#pragma region generated']);
|
||||
i++;
|
||||
for (const templateName of templateNames.split(',')) {
|
||||
var matchingTemplate = templates.find(t => t.endsWith(`${templateName}.template.c`));
|
||||
if (matchingTemplate) {
|
||||
let templateContents = fs.readFileSync(matchingTemplate, 'utf8');
|
||||
for (const arg of args) {
|
||||
[argName, argValue] = arg.split('=');
|
||||
templateContents = templateContents.replaceAll(`\$${argName}\$`, argValue);
|
||||
}
|
||||
let remainingTokens = templateContents.split('$');
|
||||
if (remainingTokens.length > 1) {
|
||||
console.log(`Error at ${file}:${i}: Template is missing parameter ${remainingTokens[1]}`)
|
||||
process.exit();
|
||||
} else {
|
||||
templateContents = templateContents.split('\n');
|
||||
lines.splice(i, 0, ...templateContents);
|
||||
i += templateContents.length;
|
||||
}
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: no template with name ${templateName}.template.c was found.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
lines.splice(i, 0, ['#pragma endregion']);
|
||||
i++;
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: template was opened and not closed again.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(file, lines.join('\n'));
|
||||
}
|
@ -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;
|
||||
@ -117,27 +117,32 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
|
||||
|
||||
if (config->left.width > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->left.color));
|
||||
SDL_RenderFillRectF(renderer, &(SDL_FRect){ boundingBox.x, boundingBox.y + config->cornerRadius.topLeft, config->left.width, boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft });
|
||||
SDL_FRect rect = { boundingBox.x, boundingBox.y + config->cornerRadius.topLeft, config->left.width, boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->right.width > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->right.color));
|
||||
SDL_RenderFillRectF(renderer, &(SDL_FRect){ boundingBox.x + boundingBox.width - config->right.width, boundingBox.y + config->cornerRadius.topRight, config->right.width, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight });
|
||||
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->right.width, boundingBox.y + config->cornerRadius.topRight, config->right.width, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->right.width > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->right.color));
|
||||
SDL_RenderFillRectF(renderer, &(SDL_FRect){ boundingBox.x + boundingBox.width - config->right.width, boundingBox.y + config->cornerRadius.topRight, config->right.width, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight });
|
||||
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->right.width, boundingBox.y + config->cornerRadius.topRight, config->right.width, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->top.width > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->right.color));
|
||||
SDL_RenderFillRectF(renderer, &(SDL_FRect){ boundingBox.x + config->cornerRadius.topLeft, boundingBox.y, boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight, config->top.width });
|
||||
SDL_FRect rect = { boundingBox.x + config->cornerRadius.topLeft, boundingBox.y, boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight, config->top.width };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->bottom.width > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->bottom.color));
|
||||
SDL_RenderFillRectF(renderer, &(SDL_FRect){ boundingBox.x + config->cornerRadius.bottomLeft, boundingBox.y + boundingBox.height - config->bottom.width, boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight, config->bottom.width });
|
||||
SDL_FRect rect = { boundingBox.x + config->cornerRadius.bottomLeft, boundingBox.y + boundingBox.height - config->bottom.width, boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight, config->bottom.width };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
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);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ typedef struct
|
||||
CustomLayoutElementType type;
|
||||
union {
|
||||
CustomLayoutElement_3DModel model;
|
||||
};
|
||||
} customData;
|
||||
} CustomLayoutElement;
|
||||
|
||||
// Get a ray trace from the screen position (i.e mouse) within a specific section of the screen
|
||||
@ -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';
|
||||
@ -216,7 +216,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
|
||||
float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f);
|
||||
Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140);
|
||||
BeginMode3D(Raylib_camera);
|
||||
DrawModel(customElement->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE); // Draw 3d model with texture
|
||||
DrawModel(customElement->customData.model.model, positionRay.position, customElement->customData.model.scale * scaleValue, WHITE); // Draw 3d model with texture
|
||||
EndMode3D();
|
||||
break;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user