mirror of
https://github.com/nicbarker/clay.git
synced 2025-05-12 13:28:07 +00:00
Compare commits
29 Commits
0cdc1fdc7a
...
df79cc9e53
Author | SHA1 | Date | |
---|---|---|---|
|
df79cc9e53 | ||
|
8718ce6303 | ||
|
c5726b68d9 | ||
|
81032d9457 | ||
|
73f30d10b6 | ||
|
8df5da50c7 | ||
|
951d785deb | ||
|
4612481d25 | ||
|
0a703de69a | ||
|
cb62db77e3 | ||
|
ea6109bd0b | ||
|
c0dac38c87 | ||
|
c3fcf6ce12 | ||
|
aba846a446 | ||
|
9d659e8abd | ||
|
ec2b3b35ff | ||
|
5f7176cdcc | ||
|
ebeef93c34 | ||
|
9b2d585499 | ||
|
81589ad29b | ||
|
16f894bb4d | ||
|
9f07f5aac8 | ||
|
01d3ab127f | ||
|
326325ffaf | ||
|
e8025cc254 | ||
|
8e7e30dda6 | ||
|
9d3fba39be | ||
|
4961f2153e | ||
|
a093730da2 |
@ -3,12 +3,38 @@ project(clay)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
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")
|
||||
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/introducing-clay-video-demo")
|
||||
add_subdirectory("examples/SDL2-video-demo")
|
||||
|
||||
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/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
|
||||
|
208
README.md
208
README.md
@ -20,65 +20,35 @@ _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), and initialize it with [Clay_Initialize(arena, dimensions)](#clay_initialize).
|
||||
|
||||
```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));
|
||||
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight });
|
||||
```
|
||||
|
||||
3. 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) {
|
||||
// Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
|
||||
// Note: Clay_String->chars is not guaranteed to be null terminated
|
||||
}
|
||||
|
||||
// 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};
|
||||
|
||||
void HandleClayErrors(Clay_ErrorData errorData) {
|
||||
// See the Clay_ErrorData struct for more information
|
||||
printf("%s", errorData.errorText.chars);
|
||||
switch(errorData.errorType) {
|
||||
// etc
|
||||
}
|
||||
}
|
||||
|
||||
// Example measure text function
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
// 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) },
|
||||
@ -89,52 +59,65 @@ 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
The above example, rendered correctly will look something like the following:
|
||||
|
||||

|
||||
@ -805,12 +788,59 @@ 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.
|
||||
|
||||
---
|
||||
|
||||
### CLAY_ID_LOCAL()
|
||||
|
||||
**Usage**
|
||||
|
||||
`CLAY(CLAY_ID_LOCAL(char* idString)) {}`
|
||||
|
||||
**Lifecycle**
|
||||
|
||||
`Clay_BeginLayout()` -> `CLAY(` -> `CLAY_ID_LOCAL()` -> `)` -> `Clay_EndLayout()`
|
||||
|
||||
**Notes**
|
||||
|
||||
**CLAY_ID_LOCAL()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration.
|
||||
|
||||
Unlike [CLAY_ID](#clay_id) which needs to be globally unique, a local ID is based on the ID of it's parent and only needs to be unique among its siblings.
|
||||
|
||||
As a result, local id is suitable for use in reusable components and loops.
|
||||
|
||||
**Examples**
|
||||
|
||||
```C
|
||||
void RenderHeaderButton(ButtonData button) {
|
||||
CLAY(
|
||||
CLAY_ID_LOCAL("HeaderButton"),
|
||||
CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16) })
|
||||
) {
|
||||
// ...children
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < headerButtons.length; i++) {
|
||||
RenderHeaderButton(headerButtons.items[i]);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
### CLAY_IDI_LOCAL()
|
||||
|
||||
`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.
|
||||
|
||||
---
|
||||
|
||||
### CLAY_LAYOUT
|
||||
|
||||
**Usage**
|
||||
|
1
bindings/cpp/README.md
Normal file
1
bindings/cpp/README.md
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/TimothyHoytBSME/ClayMan
|
@ -2,7 +2,7 @@ cp ../../clay.h clay.c;
|
||||
# Intel Mac
|
||||
clang -c -DCLAY_IMPLEMENTATION -o clay.o -static -target x86_64-apple-darwin clay.c -fPIC && ar r clay-odin/macos/clay.a clay.o;
|
||||
# ARM Mac
|
||||
clang -c -DCLAY_IMPLEMENTATION -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o;
|
||||
clang -c -DCLAY_IMPLEMENTATION -g -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o;
|
||||
# x64 Windows
|
||||
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/windows/clay.lib -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static clay.c;
|
||||
# Linux
|
||||
|
@ -22,6 +22,12 @@ String :: struct {
|
||||
chars: [^]c.char,
|
||||
}
|
||||
|
||||
StringSlice :: struct {
|
||||
length: c.int32_t,
|
||||
chars: [^]c.char,
|
||||
baseChars: [^]c.char,
|
||||
}
|
||||
|
||||
Vector2 :: [2]c.float
|
||||
|
||||
Dimensions :: struct {
|
||||
@ -178,7 +184,8 @@ ElementConfigUnion :: struct #raw_union {
|
||||
RenderCommand :: struct {
|
||||
boundingBox: BoundingBox,
|
||||
config: ElementConfigUnion,
|
||||
text: String,
|
||||
text: StringSlice,
|
||||
zIndex: i32,
|
||||
id: u32,
|
||||
commandType: RenderCommandType,
|
||||
}
|
||||
@ -305,7 +312,7 @@ foreign Clay {
|
||||
PointerOver :: proc(id: ElementId) -> bool ---
|
||||
GetElementId :: proc(id: String) -> ElementId ---
|
||||
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
|
||||
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions) ---
|
||||
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: uintptr) -> Dimensions, userData: uintptr) ---
|
||||
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
|
||||
SetDebugModeEnabled :: proc(enabled: bool) ---
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -493,8 +493,8 @@ main :: proc() {
|
||||
minMemorySize: u32 = clay.MinMemorySize()
|
||||
memory := make([^]u8, minMemorySize)
|
||||
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
|
||||
clay.SetMeasureTextFunction(measureText)
|
||||
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
|
||||
clay.SetMeasureTextFunction(measureText, 0)
|
||||
|
||||
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
|
||||
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
|
||||
|
@ -16,7 +16,7 @@ clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color {
|
||||
|
||||
raylibFonts := [10]RaylibFont{}
|
||||
|
||||
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
|
||||
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: uintptr) -> clay.Dimensions {
|
||||
// Measure string size for Font
|
||||
textSize: clay.Dimensions = {0, 0}
|
||||
|
||||
|
2
bindings/zig/README
Normal file
2
bindings/zig/README
Normal file
@ -0,0 +1,2 @@
|
||||
https://codeberg.org/Zettexe/clay-zig
|
||||
https://github.com/johan0A/clay-zig-bindings
|
78
clay.h
78
clay.h
@ -1,4 +1,4 @@
|
||||
// VERSION: 0.11
|
||||
// VERSION: 0.12
|
||||
|
||||
/*
|
||||
NOTE: In order to use this library you must define
|
||||
@ -191,6 +191,13 @@ CLAY__TYPEDEF(Clay__StringArray, struct {
|
||||
Clay_String *internalArray;
|
||||
});
|
||||
|
||||
CLAY__TYPEDEF(Clay_StringSlice, struct {
|
||||
int32_t length;
|
||||
const char *chars;
|
||||
// The source string / char* that this slice was derived from
|
||||
const char *baseChars;
|
||||
});
|
||||
|
||||
typedef struct Clay_Context Clay_Context;
|
||||
|
||||
CLAY__TYPEDEF(Clay_Arena, struct {
|
||||
@ -464,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;
|
||||
});
|
||||
@ -527,8 +535,8 @@ bool Clay_Hovered(void);
|
||||
void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
|
||||
bool Clay_PointerOver(Clay_ElementId elementId);
|
||||
Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
|
||||
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config));
|
||||
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId));
|
||||
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData);
|
||||
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, uintptr_t userData), uintptr_t userData);
|
||||
Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
|
||||
void Clay_SetDebugModeEnabled(bool enabled);
|
||||
bool Clay_IsDebugModeEnabled(void);
|
||||
@ -596,6 +604,7 @@ CLAY__TYPEDEF(Clay_BooleanWarnings, struct {
|
||||
bool maxElementsExceeded;
|
||||
bool maxRenderCommandsExceeded;
|
||||
bool maxTextMeasureCacheExceeded;
|
||||
bool textMeasurementFunctionNotSet;
|
||||
});
|
||||
|
||||
CLAY__TYPEDEF(Clay__Warning, struct {
|
||||
@ -1406,6 +1415,8 @@ struct Clay_Context {
|
||||
uint32_t debugSelectedElementId;
|
||||
uint32_t generation;
|
||||
uintptr_t arenaResetOffset;
|
||||
uintptr_t mesureTextUserData;
|
||||
uintptr_t queryScrollOffsetUserData;
|
||||
Clay_Arena internalArena;
|
||||
// Layout Elements / Render Commands
|
||||
Clay_LayoutElementArray layoutElements;
|
||||
@ -1479,11 +1490,11 @@ Clay_String Clay__WriteStringToCharBuffer(Clay__CharArray *buffer, Clay_String s
|
||||
}
|
||||
|
||||
#ifdef CLAY_WASM
|
||||
__attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_String *text, Clay_TextElementConfig *config);
|
||||
__attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId);
|
||||
__attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData);
|
||||
__attribute__((import_module("clay"), import_name("queryScrollOffsetFunction"))) Clay_Vector2 Clay__QueryScrollOffset(uint32_t elementId, uintptr_t userData);
|
||||
#else
|
||||
Clay_Dimensions (*Clay__MeasureText)(Clay_String *text, Clay_TextElementConfig *config);
|
||||
Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId);
|
||||
Clay_Dimensions (*Clay__MeasureText)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData);
|
||||
Clay_Vector2 (*Clay__QueryScrollOffset)(uint32_t elementId, uintptr_t userData);
|
||||
#endif
|
||||
|
||||
Clay_LayoutElement* Clay__GetOpenLayoutElement(void) {
|
||||
@ -1624,11 +1635,14 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
#ifndef CLAY_WASM
|
||||
if (!Clay__MeasureText) {
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
.errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,
|
||||
.errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."),
|
||||
.userData = context->errorHandler.userData });
|
||||
return NULL;
|
||||
if (!context->booleanWarnings.textMeasurementFunctionNotSet) {
|
||||
context->booleanWarnings.textMeasurementFunctionNotSet = true;
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
.errorType = CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,
|
||||
.errorText = CLAY_STRING("Clay's internal MeasureText function is null. You may have forgotten to call Clay_SetMeasureTextFunction(), or passed a NULL function pointer by mistake."),
|
||||
.userData = context->errorHandler.userData });
|
||||
}
|
||||
return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT;
|
||||
}
|
||||
#endif
|
||||
uint32_t id = Clay__HashTextWithConfig(text, config);
|
||||
@ -1695,7 +1709,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
float lineWidth = 0;
|
||||
float measuredWidth = 0;
|
||||
float measuredHeight = 0;
|
||||
float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, config).width;
|
||||
float spaceWidth = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = 1, .chars = CLAY__SPACECHAR.chars, .baseChars = CLAY__SPACECHAR.chars }, config, context->mesureTextUserData).width;
|
||||
Clay__MeasuredWord tempWord = { .next = -1 };
|
||||
Clay__MeasuredWord *previousWord = &tempWord;
|
||||
while (end < text->length) {
|
||||
@ -1712,8 +1726,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
char current = text->chars[end];
|
||||
if (current == ' ' || current == '\n') {
|
||||
int32_t length = end - start;
|
||||
Clay_String word = { .length = length, .chars = &text->chars[start] };
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(&word, config);
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->mesureTextUserData);
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
if (current == ' ') {
|
||||
dimensions.width += spaceWidth;
|
||||
@ -1735,8 +1748,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
end++;
|
||||
}
|
||||
if (end - start > 0) {
|
||||
Clay_String lastWord = { .length = end - start, .chars = &text->chars[start] };
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(&lastWord, config);
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = end - start, .chars = &text->chars[start], .baseChars = text->chars }, config, context->mesureTextUserData);
|
||||
Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord);
|
||||
lineWidth += dimensions.width;
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
@ -1898,7 +1910,7 @@ void Clay__ElementPostConfiguration(void) {
|
||||
scrollOffset = Clay__ScrollContainerDataInternalArray_Add(&context->scrollContainerDatas, CLAY__INIT(Clay__ScrollContainerDataInternal){.layoutElement = openLayoutElement, .scrollOrigin = {-1,-1}, .elementId = openLayoutElement->id, .openThisFrame = true});
|
||||
}
|
||||
if (context->externalScrollHandlingEnabled) {
|
||||
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId);
|
||||
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -2246,12 +2258,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
*childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.size.percent;
|
||||
if (sizingAlongAxis) {
|
||||
innerContentSize += *childSize;
|
||||
if (childOffset > 0) {
|
||||
innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child
|
||||
totalPaddingAndChildGaps += parentChildGap;
|
||||
}
|
||||
} else {
|
||||
innerContentSize = CLAY__MAX(*childSize, innerContentSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2472,9 +2478,6 @@ void Clay__CalculateFinalLayout() {
|
||||
|
||||
// DFS node has been visited, this is on the way back up to the root
|
||||
Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig;
|
||||
if (layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_PERCENT) {
|
||||
continue;
|
||||
}
|
||||
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
|
||||
// Resize any parent containers that have grown in height along their non layout axis
|
||||
for (int32_t j = 0; j < currentElement->childrenOrTextContent.children.length; ++j) {
|
||||
@ -2598,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,
|
||||
});
|
||||
@ -2730,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,
|
||||
});
|
||||
@ -3674,11 +3679,15 @@ Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset
|
||||
}
|
||||
|
||||
#ifndef CLAY_WASM
|
||||
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config)) {
|
||||
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData), uintptr_t userData) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay__MeasureText = measureTextFunction;
|
||||
context->mesureTextUserData = userData;
|
||||
}
|
||||
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId)) {
|
||||
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, uintptr_t userData), uintptr_t userData) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay__QueryScrollOffset = queryScrollOffsetFunction;
|
||||
context->queryScrollOffsetUserData = userData;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -3918,9 +3927,7 @@ void Clay_BeginLayout(void) {
|
||||
if (context->debugModeEnabled) {
|
||||
rootDimensions.width -= (float)Clay__debugViewWidth;
|
||||
}
|
||||
context->booleanWarnings.maxElementsExceeded = false;
|
||||
context->booleanWarnings.maxTextMeasureCacheExceeded = false;
|
||||
context->booleanWarnings.maxRenderCommandsExceeded = false;
|
||||
context->booleanWarnings = CLAY__INIT(Clay_BooleanWarnings) CLAY__DEFAULT_STRUCT;
|
||||
Clay__OpenElement();
|
||||
CLAY_ID("Clay__RootContainer");
|
||||
CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED((rootDimensions.width)), CLAY_SIZING_FIXED(rootDimensions.height)} });
|
||||
@ -3941,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();
|
||||
}
|
||||
|
@ -23,6 +23,15 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(SDL2_ttf)
|
||||
|
||||
FetchContent_Declare(
|
||||
SDL2_image
|
||||
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
|
||||
GIT_TAG "release-2.8.4"
|
||||
GIT_PROGRESS TRUE
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(SDL2_image)
|
||||
|
||||
add_executable(SDL2_video_demo main.c)
|
||||
|
||||
target_compile_options(SDL2_video_demo PUBLIC)
|
||||
@ -32,6 +41,7 @@ target_link_libraries(SDL2_video_demo PUBLIC
|
||||
SDL2::SDL2main
|
||||
SDL2::SDL2-static
|
||||
SDL2_ttf::SDL2_ttf-static
|
||||
SDL2_image::SDL2_image-static
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
|
@ -13,6 +13,8 @@
|
||||
const int FONT_ID_BODY_16 = 0;
|
||||
Clay_Color COLOR_WHITE = { 255, 255, 255, 255};
|
||||
|
||||
SDL_Surface *sample_image;
|
||||
|
||||
void RenderHeaderButton(Clay_String text) {
|
||||
CLAY(
|
||||
CLAY_LAYOUT({ .padding = { 16, 16, 8, 8 }}),
|
||||
@ -111,9 +113,18 @@ static Clay_RenderCommandArray CreateLayout() {
|
||||
})
|
||||
) {
|
||||
// Header buttons go here
|
||||
CLAY(
|
||||
CLAY_LAYOUT({ .padding = { 16, 16, 8, 8 }}),
|
||||
CLAY_BORDER_ALL({ 2, COLOR_WHITE })
|
||||
) {
|
||||
CLAY(
|
||||
CLAY_LAYOUT({ .padding = { 8, 8, 8, 8 }}),
|
||||
CLAY_IMAGE({ sample_image, { 23, 42 } })
|
||||
) {}
|
||||
}
|
||||
CLAY(
|
||||
CLAY_ID("FileButton"),
|
||||
CLAY_LAYOUT({ .padding = { 16, 8 }}),
|
||||
CLAY_LAYOUT({ .padding = { 16, 16, 8, 8 }}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 140, 140, 140, 255 },
|
||||
.cornerRadius = 5
|
||||
@ -278,17 +289,26 @@ int main(int argc, char *argv[]) {
|
||||
fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError());
|
||||
return 1;
|
||||
}
|
||||
if (IMG_Init(IMG_INIT_PNG) < 0) {
|
||||
fprintf(stderr, "Error: could not initialize IMG: %s\n", IMG_GetError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 16);
|
||||
if (!font) {
|
||||
fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError());
|
||||
return 1;
|
||||
}
|
||||
SDL2_fonts[FONT_ID_BODY_16] = (SDL2_Font) {
|
||||
|
||||
SDL2_Font fonts[1] = {};
|
||||
|
||||
fonts[FONT_ID_BODY_16] = (SDL2_Font) {
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.font = font,
|
||||
};
|
||||
|
||||
sample_image = IMG_Load("resources/sample.png");
|
||||
|
||||
SDL_Window *window = NULL;
|
||||
SDL_Renderer *renderer = NULL;
|
||||
if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) {
|
||||
@ -298,12 +318,13 @@ int main(int argc, char *argv[]) {
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
|
||||
|
||||
Clay_SetMeasureTextFunction(SDL2_MeasureText);
|
||||
|
||||
int windowWidth = 0;
|
||||
int windowHeight = 0;
|
||||
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
|
||||
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors });
|
||||
|
||||
Clay_SetMeasureTextFunction(SDL2_MeasureText, (uintptr_t)&fonts);
|
||||
|
||||
Uint64 NOW = SDL_GetPerformanceCounter();
|
||||
Uint64 LAST = 0;
|
||||
double deltaTime = 0;
|
||||
@ -344,7 +365,7 @@ int main(int argc, char *argv[]) {
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
Clay_SDL2_Render(renderer, renderCommands);
|
||||
Clay_SDL2_Render(renderer, renderCommands, fonts);
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
@ -352,6 +373,7 @@ int main(int argc, char *argv[]) {
|
||||
quit:
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
IMG_Quit();
|
||||
TTF_Quit();
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
|
BIN
examples/SDL2-video-demo/resources/sample.png
Normal file
BIN
examples/SDL2-video-demo/resources/sample.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 850 B |
49
examples/SDL3-simple-demo/CMakeLists.txt
Normal file
49
examples/SDL3-simple-demo/CMakeLists.txt
Normal file
@ -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")
|
||||
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
|
||||
)
|
215
examples/SDL3-simple-demo/main.c
Normal file
215
examples/SDL3-simple-demo/main.c
Normal file
@ -0,0 +1,215 @@
|
||||
#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_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData)
|
||||
{
|
||||
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(const Clay_String text, const int cornerRadius)
|
||||
{
|
||||
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,
|
||||
.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_BORDER({
|
||||
.left = { 20, COLOR_BLUE },
|
||||
.right = { 20, COLOR_BLUE },
|
||||
.bottom = { 20, COLOR_BLUE }
|
||||
}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = COLOR_LIGHT,
|
||||
})
|
||||
) {
|
||||
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();
|
||||
}
|
||||
|
||||
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_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
|
||||
Clay_SetMeasureTextFunction(SDL_MeasureText, 0);
|
||||
|
||||
*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();
|
||||
}
|
BIN
examples/SDL3-simple-demo/resources/Roboto-Regular.ttf
Normal file
BIN
examples/SDL3-simple-demo/resources/Roboto-Regular.ttf
Normal file
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', },
|
||||
]
|
||||
@ -314,7 +320,7 @@
|
||||
|
||||
const importObject = {
|
||||
clay: {
|
||||
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
|
||||
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
|
||||
let stringLength = memoryDataView.getUint32(textToMeasure, true);
|
||||
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
|
||||
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
|
||||
|
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', },
|
||||
]
|
||||
@ -314,7 +320,7 @@
|
||||
|
||||
const importObject = {
|
||||
clay: {
|
||||
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
|
||||
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
|
||||
let stringLength = memoryDataView.getUint32(textToMeasure, true);
|
||||
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
|
||||
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
|
||||
|
@ -84,7 +84,7 @@ int main(void) {
|
||||
.width = GetScreenWidth(),
|
||||
.height = GetScreenHeight()
|
||||
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText);
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
|
||||
Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) {
|
||||
.font = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400),
|
||||
.fontId = FONT_ID_BODY_16
|
||||
@ -117,7 +117,7 @@ int main(void) {
|
||||
|
||||
Clay_RectangleElementConfig contentBackgroundConfig = {
|
||||
.color = { 90, 90, 90, 255 },
|
||||
.cornerRadius = 8
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
};
|
||||
|
||||
Clay_BeginLayout();
|
||||
@ -141,7 +141,7 @@ int main(void) {
|
||||
.height = CLAY_SIZING_FIXED(60),
|
||||
.width = CLAY_SIZING_GROW(0)
|
||||
},
|
||||
.padding = { 16 },
|
||||
.padding = { 16, 16, 0, 0 },
|
||||
.childGap = 16,
|
||||
.childAlignment = {
|
||||
.y = CLAY_ALIGN_Y_CENTER
|
||||
@ -151,10 +151,10 @@ int main(void) {
|
||||
// Header buttons go here
|
||||
CLAY(
|
||||
CLAY_ID("FileButton"),
|
||||
CLAY_LAYOUT({ .padding = { 16, 8 }}),
|
||||
CLAY_LAYOUT({ .padding = { 16, 16, 8, 8 }}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 140, 140, 140, 255 },
|
||||
.cornerRadius = 5
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(5)
|
||||
})
|
||||
) {
|
||||
CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({
|
||||
@ -177,7 +177,7 @@ int main(void) {
|
||||
},
|
||||
}),
|
||||
CLAY_LAYOUT({
|
||||
.padding = {0, 8 }
|
||||
.padding = {0, 0, 8, 8 }
|
||||
})
|
||||
) {
|
||||
CLAY(
|
||||
@ -189,7 +189,7 @@ int main(void) {
|
||||
}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 40, 40, 40, 255 },
|
||||
.cornerRadius = 8
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
})
|
||||
) {
|
||||
// Render dropdown items here
|
||||
@ -236,7 +236,7 @@ int main(void) {
|
||||
CLAY_LAYOUT(sidebarButtonLayout),
|
||||
CLAY_RECTANGLE({
|
||||
.color = { 120, 120, 120, 255 },
|
||||
.cornerRadius = 8,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8),
|
||||
})
|
||||
) {
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
@ -252,7 +252,7 @@ int main(void) {
|
||||
Clay_Hovered()
|
||||
? CLAY_RECTANGLE({
|
||||
.color = { 120, 120, 120, 120 },
|
||||
.cornerRadius = 8
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
})
|
||||
: 0
|
||||
) {
|
||||
|
@ -247,7 +247,7 @@ int main(void) {
|
||||
.height = GetScreenHeight() / 2
|
||||
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
|
||||
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText);
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
|
||||
Raylib_fonts[FONT_ID_BODY_16] = (Raylib_Font) {
|
||||
.font = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400),
|
||||
.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} -DCLAY_DEBUG -fsanitize=address")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
endif()
|
||||
|
||||
|
@ -218,8 +218,8 @@ void HandleClayErrors(Clay_ErrorData errorData) {
|
||||
int main(void) {
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText);
|
||||
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors });
|
||||
Clay_SetMeasureTextFunction(Raylib_MeasureText, 0);
|
||||
Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);
|
||||
profilePicture = LoadTextureFromImage(LoadImage("resources/profile-picture.png"));
|
||||
Raylib_fonts[FONT_ID_BODY_24] = (Raylib_Font) {
|
||||
|
@ -57,7 +57,7 @@ def main() -> None:
|
||||
logger.info(f'Generator: {args.generator}')
|
||||
|
||||
logger.info('Parsing headers')
|
||||
extracted_symbols = parse_headers(input_files, output_dir, tmp_dir)
|
||||
extracted_symbols = parse_headers(input_files, tmp_dir)
|
||||
with open(tmp_dir / 'extracted_symbols.json', 'w') as f:
|
||||
f.write(json.dumps({
|
||||
'structs': extracted_symbols.structs,
|
||||
|
@ -37,6 +37,7 @@ class BaseGenerator:
|
||||
|
||||
def write_outputs(self, output_dir: Path) -> None:
|
||||
for file_name, content in self.output_content.items():
|
||||
(output_dir / file_name).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_dir / file_name, 'w') as f:
|
||||
f.write("\n".join(content))
|
||||
|
||||
|
@ -40,6 +40,12 @@ SizingConstraints :: struct #raw_union {
|
||||
sizePercent: c.float,
|
||||
}
|
||||
|
||||
TypedConfig :: struct {
|
||||
type: ElementConfigType,
|
||||
config: rawptr,
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
{{structs}}
|
||||
|
||||
@(link_prefix = "Clay_", default_calling_convention = "c")
|
||||
|
@ -1,6 +1,7 @@
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from parser import ExtractedSymbolType
|
||||
from generators.base_generator import BaseGenerator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -60,27 +61,20 @@ TYPE_MAPPING = {
|
||||
'uint32_t': 'u32',
|
||||
'int32_t': 'c.int32_t',
|
||||
'uintptr_t': 'rawptr',
|
||||
'intptr_t': 'rawptr',
|
||||
'void': 'void',
|
||||
|
||||
'*Clay_RectangleElementConfig': '^RectangleElementConfig',
|
||||
'*Clay_TextElementConfig': '^TextElementConfig',
|
||||
'*Clay_ImageElementConfig': '^ImageElementConfig',
|
||||
'*Clay_FloatingElementConfig': '^FloatingElementConfig',
|
||||
'*Clay_CustomElementConfig': '^CustomElementConfig',
|
||||
'*Clay_ScrollElementConfig': '^ScrollElementConfig',
|
||||
'*Clay_BorderElementConfig': '^BorderElementConfig',
|
||||
}
|
||||
STRUCT_TYPE_OVERRIDES = {
|
||||
'Clay_Arena': {
|
||||
'nextAllocation': 'uintptr',
|
||||
'capacity': 'uintptr',
|
||||
},
|
||||
'Clay_ErrorHandler': {
|
||||
'errorHandlerFunction': 'proc "c" (errorData: ErrorData)',
|
||||
},
|
||||
'Clay_SizingAxis': {
|
||||
'size': 'SizingConstraints',
|
||||
},
|
||||
"Clay_RenderCommand": {
|
||||
"zIndex": 'i32',
|
||||
},
|
||||
}
|
||||
STRUCT_MEMBER_OVERRIDES = {
|
||||
'Clay_ErrorHandler': {
|
||||
@ -105,7 +99,13 @@ FUNCTION_TYPE_OVERRIDES = {
|
||||
'offset': '[^]u8',
|
||||
},
|
||||
'Clay_SetMeasureTextFunction': {
|
||||
'measureTextFunction': 'proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions',
|
||||
'userData': 'uintptr',
|
||||
},
|
||||
'Clay_RenderCommandArray_Get': {
|
||||
'index': 'i32',
|
||||
},
|
||||
"Clay__AttachElementConfig": {
|
||||
"config": 'rawptr',
|
||||
},
|
||||
}
|
||||
|
||||
@ -145,23 +145,53 @@ class OdinGenerator(BaseGenerator):
|
||||
return base_name
|
||||
raise ValueError(f'Unknown symbol: {symbol}')
|
||||
|
||||
def resolve_binding_type(self, symbol: str, member: str | None, member_type: str | None, type_overrides: dict[str, dict[str, str]]) -> str | None:
|
||||
if member_type in SYMBOL_COMPLETE_OVERRIDES:
|
||||
return SYMBOL_COMPLETE_OVERRIDES[member_type]
|
||||
if symbol in type_overrides and member in type_overrides[symbol]:
|
||||
return type_overrides[symbol][member]
|
||||
if member_type in TYPE_MAPPING:
|
||||
return TYPE_MAPPING[member_type]
|
||||
if member_type and self.has_symbol(member_type):
|
||||
return self.get_symbol_name(member_type)
|
||||
if member_type and member_type.startswith('*'):
|
||||
result = self.resolve_binding_type(symbol, member, member_type[1:], type_overrides)
|
||||
if result:
|
||||
return f"^{result}"
|
||||
return None
|
||||
def format_type(self, type: ExtractedSymbolType) -> str:
|
||||
if isinstance(type, str):
|
||||
return type
|
||||
|
||||
parameter_strs = []
|
||||
for param_name, param_type in type['params']:
|
||||
parameter_strs.append(f"{param_name}: {self.format_type(param_type or 'unknown')}")
|
||||
return_type_str = ''
|
||||
if type['return_type'] is not None and type['return_type'] != 'void':
|
||||
return_type_str = ' -> ' + self.format_type(type['return_type'])
|
||||
return f"proc \"c\" ({', '.join(parameter_strs)}){return_type_str}"
|
||||
|
||||
def resolve_binding_type(self, symbol: str, member: str | None, member_type: ExtractedSymbolType | None, type_overrides: dict[str, dict[str, str]]) -> str | None:
|
||||
if isinstance(member_type, str):
|
||||
if member_type in SYMBOL_COMPLETE_OVERRIDES:
|
||||
return SYMBOL_COMPLETE_OVERRIDES[member_type]
|
||||
if symbol in type_overrides and member in type_overrides[symbol]:
|
||||
return type_overrides[symbol][member]
|
||||
if member_type in TYPE_MAPPING:
|
||||
return TYPE_MAPPING[member_type]
|
||||
if member_type and self.has_symbol(member_type):
|
||||
return self.get_symbol_name(member_type)
|
||||
if member_type and member_type.startswith('*'):
|
||||
result = self.resolve_binding_type(symbol, member, member_type[1:], type_overrides)
|
||||
if result:
|
||||
return f"^{result}"
|
||||
return None
|
||||
if member_type is None:
|
||||
return None
|
||||
|
||||
resolved_parameters = []
|
||||
for param_name, param_type in member_type['params']:
|
||||
resolved_param = self.resolve_binding_type(symbol, param_name, param_type, type_overrides)
|
||||
if resolved_param is None:
|
||||
return None
|
||||
resolved_parameters.append((param_name, resolved_param))
|
||||
resolved_return_type = self.resolve_binding_type(symbol, None, member_type['return_type'], type_overrides)
|
||||
if resolved_return_type is None:
|
||||
return None
|
||||
return self.format_type({
|
||||
"params": resolved_parameters,
|
||||
"return_type": resolved_return_type,
|
||||
})
|
||||
|
||||
def generate_structs(self) -> None:
|
||||
for struct, members in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]):
|
||||
for struct, struct_data in sorted(self.extracted_symbols.structs.items(), key=lambda x: x[0]):
|
||||
members = struct_data['attrs']
|
||||
if not struct.startswith('Clay_'):
|
||||
continue
|
||||
if struct in SYMBOL_COMPLETE_OVERRIDES:
|
||||
@ -173,12 +203,15 @@ class OdinGenerator(BaseGenerator):
|
||||
|
||||
if struct in STRUCT_OVERRIDE_AS_FIXED_ARRAY:
|
||||
array_size = len(members)
|
||||
array_type = list(members.values())[0]['type']
|
||||
first_elem = list(members.values())[0]
|
||||
array_type = None
|
||||
if 'type' in first_elem:
|
||||
array_type = first_elem['type']
|
||||
|
||||
if array_type in TYPE_MAPPING:
|
||||
array_binding_type = TYPE_MAPPING[array_type]
|
||||
elif array_type and self.has_symbol(array_type):
|
||||
array_binding_type = self.get_symbol_name(array_type)
|
||||
elif array_type and self.has_symbol(self.format_type(array_type)):
|
||||
array_binding_type = self.get_symbol_name(self.format_type(array_type))
|
||||
else:
|
||||
self._write('struct', f"// {struct} ({array_type}) - has no mapping")
|
||||
continue
|
||||
@ -188,8 +221,10 @@ class OdinGenerator(BaseGenerator):
|
||||
self._write('struct', "")
|
||||
continue
|
||||
|
||||
raw_union = ' #raw_union' if struct_data.get('is_union', False) else ''
|
||||
|
||||
self._write('struct', f"// {struct}")
|
||||
self._write('struct', f"{binding_name} :: struct {{")
|
||||
self._write('struct', f"{binding_name} :: struct{raw_union} {{")
|
||||
|
||||
for member, member_info in members.items():
|
||||
if struct in STRUCT_TYPE_OVERRIDES and member in STRUCT_TYPE_OVERRIDES[struct]:
|
||||
@ -275,5 +310,6 @@ class OdinGenerator(BaseGenerator):
|
||||
continue
|
||||
|
||||
binding_params_str = ', '.join(binding_params)
|
||||
self._write(write_to, f" {binding_name} :: proc({binding_params_str}) -> {binding_return_type} --- // {function}")
|
||||
return_str = f" -> {binding_return_type}" if binding_return_type != 'void' else ''
|
||||
self._write(write_to, f" {binding_name} :: proc({binding_params_str}){return_str} --- // {function}")
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, TypedDict
|
||||
from typing import Optional, TypedDict, NotRequired, Union
|
||||
from pycparser import c_ast, parse_file, preprocess_file
|
||||
from pathlib import Path
|
||||
import os
|
||||
@ -9,19 +9,24 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ExtractedSymbolType = Union[str, "ExtractedFunction"]
|
||||
|
||||
class ExtractedStructAttributeUnion(TypedDict):
|
||||
type: Optional[str]
|
||||
type: Optional[ExtractedSymbolType]
|
||||
|
||||
class ExtractedStructAttribute(TypedDict):
|
||||
type: Optional[str]
|
||||
union: Optional[dict[str, Optional[str]]]
|
||||
type: NotRequired[ExtractedSymbolType]
|
||||
union: NotRequired[dict[str, Optional[ExtractedSymbolType]]]
|
||||
|
||||
class ExtractedStruct(TypedDict):
|
||||
attrs: dict[str, ExtractedStructAttribute]
|
||||
is_union: NotRequired[bool]
|
||||
|
||||
ExtractedStruct = dict[str, ExtractedStructAttribute]
|
||||
ExtractedEnum = dict[str, Optional[str]]
|
||||
ExtractedFunctionParam = tuple[str, Optional[str]]
|
||||
ExtractedFunctionParam = tuple[str, Optional[ExtractedSymbolType]]
|
||||
|
||||
class ExtractedFunction(TypedDict):
|
||||
return_type: Optional[str]
|
||||
return_type: Optional["ExtractedSymbolType"]
|
||||
params: list[ExtractedFunctionParam]
|
||||
|
||||
@dataclass
|
||||
@ -30,11 +35,21 @@ class ExtractedSymbols:
|
||||
enums: dict[str, ExtractedEnum]
|
||||
functions: dict[str, ExtractedFunction]
|
||||
|
||||
def get_type_names(node: c_ast.Node, prefix: str="") -> Optional[str]:
|
||||
def get_type_names(node: c_ast.Node, prefix: str="") -> Optional[ExtractedSymbolType]:
|
||||
if isinstance(node, c_ast.TypeDecl) and hasattr(node, 'quals') and node.quals:
|
||||
prefix = " ".join(node.quals) + " " + prefix
|
||||
if isinstance(node, c_ast.PtrDecl):
|
||||
prefix = "*" + prefix
|
||||
if isinstance(node, c_ast.FuncDecl):
|
||||
func: ExtractedFunction = {
|
||||
'return_type': get_type_names(node.type),
|
||||
'params': [],
|
||||
}
|
||||
for param in node.args.params:
|
||||
if param.name is None:
|
||||
continue
|
||||
func['params'].append((param.name, get_type_names(param)))
|
||||
return func
|
||||
|
||||
if hasattr(node, 'names'):
|
||||
return prefix + node.names[0] # type: ignore
|
||||
@ -50,7 +65,7 @@ class Visitor(c_ast.NodeVisitor):
|
||||
|
||||
def visit_FuncDecl(self, node: c_ast.FuncDecl):
|
||||
# node.show()
|
||||
logger.debug(node)
|
||||
# logger.debug(node)
|
||||
node_type = node.type
|
||||
is_pointer = False
|
||||
if isinstance(node.type, c_ast.PtrDecl):
|
||||
@ -59,7 +74,7 @@ class Visitor(c_ast.NodeVisitor):
|
||||
|
||||
if hasattr(node_type, "declname"):
|
||||
return_type = get_type_names(node_type.type)
|
||||
if return_type is not None and is_pointer:
|
||||
if return_type is not None and isinstance(return_type, str) and is_pointer:
|
||||
return_type = "*" + return_type
|
||||
func: ExtractedFunction = {
|
||||
'return_type': return_type,
|
||||
@ -80,7 +95,9 @@ class Visitor(c_ast.NodeVisitor):
|
||||
struct[decl.name] = {
|
||||
"type": get_type_names(decl),
|
||||
}
|
||||
self.structs[node.name] = struct
|
||||
self.structs[node.name] = {
|
||||
'attrs': struct,
|
||||
}
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_Typedef(self, node: c_ast.Typedef):
|
||||
@ -99,7 +116,11 @@ class Visitor(c_ast.NodeVisitor):
|
||||
struct[decl.name] = {
|
||||
"type": get_type_names(decl),
|
||||
}
|
||||
self.structs[node.name] = struct
|
||||
|
||||
self.structs[node.name] = {
|
||||
'attrs': struct,
|
||||
'is_union': isinstance(node.type.type, c_ast.Union),
|
||||
}
|
||||
if hasattr(node.type, 'type') and isinstance(node.type.type, c_ast.Enum):
|
||||
enum = {}
|
||||
for enumerator in node.type.type.values.enumerators:
|
||||
@ -111,7 +132,7 @@ class Visitor(c_ast.NodeVisitor):
|
||||
self.generic_visit(node)
|
||||
|
||||
|
||||
def parse_headers(input_files: list[Path], output_dir: Path, tmp_dir: Path) -> ExtractedSymbols:
|
||||
def parse_headers(input_files: list[Path], tmp_dir: Path) -> ExtractedSymbols:
|
||||
cpp_args = ["-nostdinc", "-D__attribute__(x)=", "-E"]
|
||||
|
||||
# Make a new clay.h that combines the provided input files, so that we can add bindings for customized structs
|
||||
|
@ -1,7 +1,5 @@
|
||||
Please note, the SDL2 renderer is not 100% feature complete. It is currently missing:
|
||||
|
||||
- Border rendering
|
||||
- Image rendering
|
||||
- Rounded rectangle corners
|
||||
|
||||
Note: on Mac OSX, SDL2 for some reason decides to automatically disable momentum scrolling on macbook trackpads.
|
||||
@ -10,4 +8,4 @@ You can re enable it in objective C using:
|
||||
```C
|
||||
[[NSUserDefaults standardUserDefaults] setBool: YES
|
||||
forKey: @"AppleMomentumScrollSupported"];
|
||||
```
|
||||
```
|
||||
|
@ -1,21 +1,25 @@
|
||||
#include "../../clay.h"
|
||||
#include <SDL.h>
|
||||
#include <SDL_ttf.h>
|
||||
#include <SDL_image.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define CLAY_COLOR_TO_SDL_COLOR_ARGS(color) color.r, color.g, color.b, color.a
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t fontId;
|
||||
TTF_Font *font;
|
||||
} SDL2_Font;
|
||||
|
||||
static SDL2_Font SDL2_fonts[1];
|
||||
|
||||
static Clay_Dimensions SDL2_MeasureText(Clay_String *text, Clay_TextElementConfig *config)
|
||||
static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData)
|
||||
{
|
||||
TTF_Font *font = SDL2_fonts[config->fontId].font;
|
||||
char *chars = (char *)calloc(text->length + 1, 1);
|
||||
memcpy(chars, text->chars, text->length);
|
||||
SDL2_Font *fonts = (SDL2_Font*)userData;
|
||||
|
||||
TTF_Font *font = fonts[config->fontId].font;
|
||||
char *chars = (char *)calloc(text.length + 1, 1);
|
||||
memcpy(chars, text.chars, text.length);
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if (TTF_SizeUTF8(font, chars, &width, &height) < 0) {
|
||||
@ -31,7 +35,7 @@ static Clay_Dimensions SDL2_MeasureText(Clay_String *text, Clay_TextElementConfi
|
||||
|
||||
SDL_Rect currentClippingRectangle;
|
||||
|
||||
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands)
|
||||
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands, SDL2_Font *fonts)
|
||||
{
|
||||
for (uint32_t i = 0; i < renderCommands.length; i++)
|
||||
{
|
||||
@ -54,10 +58,10 @@ 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 = SDL2_fonts[config->fontId].font;
|
||||
TTF_Font* font = fonts[config->fontId].font;
|
||||
SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) {
|
||||
.r = (Uint8)config->textColor.r,
|
||||
.g = (Uint8)config->textColor.g,
|
||||
@ -93,10 +97,55 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
|
||||
SDL_RenderSetClipRect(renderer, NULL);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
SDL_Surface *image = (SDL_Surface *)renderCommand->config.imageElementConfig->imageData;
|
||||
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, image);
|
||||
|
||||
SDL_Rect destination = (SDL_Rect){
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
|
||||
SDL_RenderCopy(renderer, texture, NULL, &destination);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderElementConfig *config = renderCommand->config.borderElementConfig;
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
renderers/SDL3/README
Normal file
4
renderers/SDL3/README
Normal file
@ -0,0 +1,4 @@
|
||||
Please note, the SDL3 renderer is not 100% feature complete. It is currently missing:
|
||||
|
||||
- Images
|
||||
- Scroll / Scissor handling
|
242
renderers/SDL3/clay_renderer_SDL3.c
Normal file
242
renderers/SDL3/clay_renderer_SDL3.c
Normal file
@ -0,0 +1,242 @@
|
||||
#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];
|
||||
/* 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);
|
||||
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: {
|
||||
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: {
|
||||
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);
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
SDL_RenderTexture(renderer, texture, NULL, &rect);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -83,7 +83,7 @@ static inline char *Clay_Cairo__NullTerminate(Clay_String *str) {
|
||||
}
|
||||
|
||||
// Measure text using cairo's *toy* text API.
|
||||
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_String *str, Clay_TextElementConfig *config) {
|
||||
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_String *str, Clay_TextElementConfig *config, uintptr_t userData) {
|
||||
// Edge case: Clay computes the width of a whitespace character
|
||||
// once. Cairo does not factor in whitespaces when computing text
|
||||
// extents, this edge-case serves as a short-circuit to introduce
|
||||
|
@ -87,10 +87,8 @@ Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int scre
|
||||
return ray;
|
||||
}
|
||||
|
||||
uint32_t measureCalls = 0;
|
||||
|
||||
static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
|
||||
measureCalls++;
|
||||
static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) {
|
||||
// Measure string size for Font
|
||||
Clay_Dimensions textSize = { 0 };
|
||||
|
||||
@ -99,16 +97,19 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextEle
|
||||
|
||||
float textHeight = config->fontSize;
|
||||
Font fontToUse = Raylib_fonts[config->fontId].font;
|
||||
// Font failed to load, likely the fonts are in the wrong place relative to the execution dir
|
||||
if (!fontToUse.glyphs) return textSize;
|
||||
|
||||
float scaleFactor = config->fontSize/(float)fontToUse.baseSize;
|
||||
|
||||
for (int i = 0; i < text->length; ++i)
|
||||
for (int i = 0; i < text.length; ++i)
|
||||
{
|
||||
if (text->chars[i] == '\n') {
|
||||
if (text.chars[i] == '\n') {
|
||||
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
|
||||
lineTextWidth = 0;
|
||||
continue;
|
||||
}
|
||||
int index = text->chars[i] - 32;
|
||||
int index = text.chars[i] - 32;
|
||||
if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX;
|
||||
else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX);
|
||||
}
|
||||
@ -129,7 +130,6 @@ void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned i
|
||||
|
||||
void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
|
||||
{
|
||||
measureCalls = 0;
|
||||
for (int j = 0; j < renderCommands.length; j++)
|
||||
{
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
|
||||
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user