mirror of
https://github.com/nicbarker/clay.git
synced 2025-01-23 01:46:02 +00:00
Compare commits
3 Commits
2639c015f7
...
5b09727d29
Author | SHA1 | Date | |
---|---|---|---|
|
5b09727d29 | ||
|
8e7e30dda6 | ||
|
6cd721e647 |
@ -3,12 +3,17 @@ project(clay)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
if(APPLE)
|
||||
enable_language(OBJC)
|
||||
endif()
|
||||
|
||||
add_subdirectory("examples/cpp-project-example")
|
||||
add_subdirectory("examples/raylib-multi-context")
|
||||
add_subdirectory("examples/raylib-sidebar-scrolling-container")
|
||||
# add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
|
||||
if(NOT MSVC)
|
||||
add_subdirectory("examples/clay-official-website")
|
||||
add_subdirectory("examples/SDL3-simple-demo")
|
||||
endif()
|
||||
add_subdirectory("examples/introducing-clay-video-demo")
|
||||
add_subdirectory("examples/SDL2-video-demo")
|
||||
|
@ -271,6 +271,18 @@ TypedConfig :: struct {
|
||||
id: ElementId,
|
||||
}
|
||||
|
||||
PointerDataInteractionState :: enum EnumBackingType {
|
||||
PRESSED_THIS_FRAME,
|
||||
PRESSED,
|
||||
RELEASED_THIS_FRAME,
|
||||
RELEASED,
|
||||
}
|
||||
|
||||
PointerData :: struct {
|
||||
position: Vector2,
|
||||
state: PointerDataInteractionState,
|
||||
}
|
||||
|
||||
ErrorType :: enum {
|
||||
TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,
|
||||
ARENA_CAPACITY_EXCEEDED,
|
||||
@ -284,12 +296,12 @@ ErrorType :: enum {
|
||||
ErrorData :: struct {
|
||||
errorType: ErrorType,
|
||||
errorText: String,
|
||||
userData: rawptr
|
||||
userData: rawptr,
|
||||
}
|
||||
|
||||
ErrorHandler :: struct {
|
||||
handler: proc "c" (errorData: ErrorData),
|
||||
userData: rawptr
|
||||
handler: proc "c" (errorData: ErrorData),
|
||||
userData: rawptr,
|
||||
}
|
||||
|
||||
@(link_prefix = "Clay_", default_calling_convention = "c")
|
||||
@ -302,24 +314,31 @@ foreign Clay {
|
||||
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
|
||||
BeginLayout :: proc() ---
|
||||
EndLayout :: proc() -> ClayArray(RenderCommand) ---
|
||||
PointerOver :: proc(id: ElementId) -> bool ---
|
||||
GetElementId :: proc(id: String) -> ElementId ---
|
||||
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
|
||||
Hovered :: proc() -> bool ---
|
||||
OnHover :: proc(onHoverFunction: proc "c" (elementId: ElementId, pointerInfo: PointerData, userData: rawptr), userData: rawptr) ---
|
||||
PointerOver :: proc(id: ElementId) -> bool ---
|
||||
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
|
||||
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions) ---
|
||||
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32) -> Vector2) ---
|
||||
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
|
||||
SetDebugModeEnabled :: proc(enabled: bool) ---
|
||||
IsDebugModeEnabled :: proc() -> bool ---
|
||||
SetCullingEnabled :: proc(enabled: bool) ---
|
||||
SetMaxElementCount :: proc(maxElementCount: i32) ---
|
||||
SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) ---
|
||||
}
|
||||
|
||||
@(link_prefix = "Clay_", default_calling_convention = "c", private)
|
||||
foreign Clay {
|
||||
_OpenElement :: proc() ---
|
||||
_CloseElement :: proc() ---
|
||||
_StoreLayoutConfig :: proc(config: LayoutConfig) -> ^LayoutConfig ---
|
||||
_ElementPostConfiguration :: proc() ---
|
||||
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
|
||||
_AttachId :: proc(id: ElementId) ---
|
||||
_AttachLayoutConfig :: proc(layoutConfig: ^LayoutConfig) ---
|
||||
_AttachElementConfig :: proc(config: rawptr, type: ElementConfigType) ---
|
||||
_StoreLayoutConfig :: proc(config: LayoutConfig) -> ^LayoutConfig ---
|
||||
_StoreRectangleElementConfig :: proc(config: RectangleElementConfig) -> ^RectangleElementConfig ---
|
||||
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
|
||||
_StoreImageElementConfig :: proc(config: ImageElementConfig) -> ^ImageElementConfig ---
|
||||
@ -328,7 +347,8 @@ foreign Clay {
|
||||
_StoreScrollElementConfig :: proc(config: ScrollElementConfig) -> ^ScrollElementConfig ---
|
||||
_StoreBorderElementConfig :: proc(config: BorderElementConfig) -> ^BorderElementConfig ---
|
||||
_HashString :: proc(toHash: String, index: u32, seed: u32) -> ElementId ---
|
||||
_GetOpenLayoutElementId :: proc() -> u32 ---
|
||||
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
|
||||
_GetParentElementId :: proc() -> u32 ---
|
||||
}
|
||||
|
||||
@(require_results, deferred_none = _CloseElement)
|
||||
@ -349,7 +369,7 @@ UI :: proc(configs: ..TypedConfig) -> bool {
|
||||
}
|
||||
|
||||
Layout :: proc(config: LayoutConfig) -> TypedConfig {
|
||||
return {type = ElementConfigType.Layout, config = _StoreLayoutConfig(config) }
|
||||
return {type = ElementConfigType.Layout, config = _StoreLayoutConfig(config)}
|
||||
}
|
||||
|
||||
PaddingAll :: proc (padding: u16) -> Padding {
|
||||
@ -389,23 +409,35 @@ Border :: proc(config: BorderElementConfig) -> TypedConfig {
|
||||
}
|
||||
|
||||
BorderOutside :: proc(outsideBorders: BorderData) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}) }
|
||||
return {
|
||||
type = ElementConfigType.Border,
|
||||
config = _StoreBorderElementConfig((BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders}),
|
||||
}
|
||||
}
|
||||
|
||||
BorderOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
) }
|
||||
return {
|
||||
type = ElementConfigType.Border,
|
||||
config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
BorderAll :: proc(allBorders: BorderData) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig((BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}) }
|
||||
return {
|
||||
type = ElementConfigType.Border,
|
||||
config = _StoreBorderElementConfig((BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders}),
|
||||
}
|
||||
}
|
||||
|
||||
BorderAllRadius :: proc(allBorders: BorderData, radius: f32) -> TypedConfig {
|
||||
return { type = ElementConfigType.Border, config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
) }
|
||||
return {
|
||||
type = ElementConfigType.Border,
|
||||
config = _StoreBorderElementConfig(
|
||||
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
|
||||
@ -433,5 +465,5 @@ MakeString :: proc(label: string) -> String {
|
||||
}
|
||||
|
||||
ID :: proc(label: string, index: u32 = 0) -> TypedConfig {
|
||||
return { type = ElementConfigType.Id, id = _HashString(MakeString(label), index, 0) }
|
||||
return {type = ElementConfigType.Id, id = _HashString(MakeString(label), index, 0)}
|
||||
}
|
||||
|
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 -Wall -Werror")
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3")
|
||||
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
# Download SDL3
|
||||
FetchContent_Declare(
|
||||
SDL
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||
GIT_TAG preview-3.1.6
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
message(STATUS "Using SDL via FetchContent")
|
||||
FetchContent_MakeAvailable(SDL)
|
||||
set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
# Download SDL_ttf
|
||||
FetchContent_Declare(
|
||||
SDL_ttf
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
|
||||
GIT_TAG main # Slightly risky to use main branch, but it's the only one available
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
message(STATUS "Using SDL_ttf via FetchContent")
|
||||
FetchContent_MakeAvailable(SDL_ttf)
|
||||
set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
|
||||
|
||||
# Example executable
|
||||
add_executable(${PROJECT_NAME} main.c)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
SDL3::SDL3
|
||||
SDL3_ttf::SDL3_ttf
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources
|
||||
)
|
185
examples/SDL3-simple-demo/main.c
Normal file
185
examples/SDL3-simple-demo/main.c
Normal file
@ -0,0 +1,185 @@
|
||||
#define SDL_MAIN_USE_CALLBACKS
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "../../clay.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../../renderers/SDL3/clay_renderer_SDL3.c"
|
||||
|
||||
static const Uint32 FONT_ID = 0;
|
||||
|
||||
static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
|
||||
static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
|
||||
static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
|
||||
|
||||
typedef struct app_state {
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
} AppState;
|
||||
|
||||
static inline Clay_Dimensions SDL_MeasureText(Clay_String *text, Clay_TextElementConfig *config)
|
||||
{
|
||||
TTF_Font *font = gFonts[config->fontId];
|
||||
int width, height;
|
||||
|
||||
if (!TTF_GetStringSize(font, text->chars, text->length, &width, &height)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
return (Clay_Dimensions) { (float) width, (float) height };
|
||||
}
|
||||
|
||||
static void Label(Clay_String text)
|
||||
{
|
||||
CLAY(CLAY_LAYOUT({ .padding = {16, 8} }), CLAY_RECTANGLE({ .color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE })) {
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
|
||||
.textColor = { 255, 255, 255, 255 },
|
||||
.fontId = FONT_ID,
|
||||
.fontSize = 24,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
static Clay_RenderCommandArray Clay_CreateLayout()
|
||||
{
|
||||
Clay_BeginLayout();
|
||||
CLAY(CLAY_ID("MainContent"),
|
||||
CLAY_LAYOUT({
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_GROW(),
|
||||
.height = CLAY_SIZING_GROW(),
|
||||
},
|
||||
.childAlignment = {
|
||||
.x = CLAY_ALIGN_X_CENTER,
|
||||
.y = CLAY_ALIGN_Y_CENTER,
|
||||
},
|
||||
.childGap = 10,
|
||||
.padding = { 10, 10 },
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
}),
|
||||
CLAY_RECTANGLE({
|
||||
.color = COLOR_LIGHT,
|
||||
})
|
||||
) {
|
||||
Label(CLAY_STRING("Button 1"));
|
||||
Label(CLAY_STRING("Button 2"));
|
||||
Label(CLAY_STRING("Button 3"));
|
||||
}
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
||||
void HandleClayErrors(Clay_ErrorData errorData) {
|
||||
printf("%s", errorData.errorText.chars);
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
if (!TTF_Init()) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
AppState *state = SDL_calloc(1, sizeof(AppState));
|
||||
if (!state) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
*appstate = state;
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->renderer)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
SDL_SetWindowResizable(state->window, true);
|
||||
|
||||
TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24);
|
||||
if (!font) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
gFonts[FONT_ID] = font;
|
||||
|
||||
/* Initialize Clay */
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena clayMemory = (Clay_Arena) {
|
||||
.memory = SDL_malloc(totalMemorySize),
|
||||
.capacity = totalMemorySize
|
||||
};
|
||||
|
||||
int width, height;
|
||||
SDL_GetWindowSize(state->window, &width, &height);
|
||||
Clay_SetMeasureTextFunction(SDL_MeasureText);
|
||||
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
|
||||
|
||||
*appstate = state;
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
|
||||
{
|
||||
SDL_AppResult ret_val = SDL_APP_CONTINUE;
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
ret_val = SDL_APP_SUCCESS;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_RESIZED:
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 });
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
|
||||
event->motion.state & SDL_BUTTON_LEFT);
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->motion.xrel, event->motion.yrel }, 0.01f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
{
|
||||
AppState *state = appstate;
|
||||
|
||||
Clay_RenderCommandArray render_commands = Clay_CreateLayout();
|
||||
|
||||
SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(state->renderer);
|
||||
|
||||
SDL_RenderClayCommands(state->renderer, &render_commands);
|
||||
|
||||
SDL_RenderPresent(state->renderer);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void SDL_AppQuit(void *appstate, SDL_AppResult result)
|
||||
{
|
||||
(void) result;
|
||||
|
||||
if (result != SDL_APP_SUCCESS) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run");
|
||||
}
|
||||
|
||||
AppState *state = appstate;
|
||||
|
||||
if (state) {
|
||||
if (state->renderer)
|
||||
SDL_DestroyRenderer(state->renderer);
|
||||
|
||||
if (state->window)
|
||||
SDL_DestroyWindow(state->window);
|
||||
|
||||
SDL_free(state);
|
||||
}
|
||||
TTF_Quit();
|
||||
}
|
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.
6
renderers/SDL3/README
Normal file
6
renderers/SDL3/README
Normal file
@ -0,0 +1,6 @@
|
||||
Please note, the SDL3 renderer is not 100% feature complete. It is currently missing:
|
||||
|
||||
- Rounded rectangle corners
|
||||
- Borders
|
||||
- Images
|
||||
- Scroll / Scissor handling
|
41
renderers/SDL3/clay_renderer_SDL3.c
Normal file
41
renderers/SDL3/clay_renderer_SDL3.c
Normal file
@ -0,0 +1,41 @@
|
||||
#include "../../clay.h"
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
|
||||
/* This needs to be global because the "MeasureText" callback doesn't have a
|
||||
* user data parameter */
|
||||
static TTF_Font *gFonts[1];
|
||||
|
||||
static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArray *rcommands)
|
||||
{
|
||||
for (size_t i = 0; i < rcommands->length; i++) {
|
||||
Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i);
|
||||
Clay_BoundingBox bounding_box = rcmd->boundingBox;
|
||||
const SDL_FRect rect = { bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height };
|
||||
|
||||
switch (rcmd->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleElementConfig *config = rcmd->config.rectangleElementConfig;
|
||||
Clay_Color color = config->color;
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
} break;
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_TextElementConfig *config = rcmd->config.textElementConfig;
|
||||
Clay_String *text = &rcmd->text;
|
||||
SDL_Color color = { config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a };
|
||||
|
||||
TTF_Font *font = gFonts[config->fontId];
|
||||
SDL_Surface *surface = TTF_RenderText_Blended(font, text->chars, text->length, color);
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
SDL_RenderTexture(renderer, texture, NULL, &rect);
|
||||
|
||||
SDL_DestroySurface(surface);
|
||||
SDL_DestroyTexture(texture);
|
||||
} break;
|
||||
default:
|
||||
SDL_Log("Unknown render command type: %d", rcmd->commandType);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user