Compare commits

...

79 Commits
v0.13 ... main

Author SHA1 Message Date
Tim Lügger
5391a259f3
[Renderers/Raylib] Fix raylib renderer border bottom left corner radius (#378)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-14 14:26:12 +12:00
Lily Nikitin
fe2d44a888
[Renderers/Raylib] Add explicit type cast for malloc (#379) 2025-04-14 14:23:13 +12:00
Nic Barker
06167b4f4b [Core] Fix a potential null pointer deref in scroll GetScrollContainerData
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-12 11:27:10 +12:00
Nathan Korth
eb46688b82
[Renderers/Sokol] Sokol renderer & examples (#373)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-09 13:40:22 +12:00
Philosoph228
87efc49f52
[Renderers/WinGDI] Working on Win32 GDI renderer and example (#344)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-04-09 11:31:33 +12:00
Nic Barker
a9e94e3be0 [Core] Fix onHover reference not being reset for identical IDs between frames
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-04 13:05:31 +13:00
Nic Barker
cbb50267da [CMake] Revert change to CMakeLists because of OSX problems 2025-04-04 12:59:57 +13:00
Vitalii Rohozhyn
55792fdbec
[Cmake] basic CMake support for easier import into CMake projects (#345)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-01 10:48:50 +13:00
Nic Barker
50aad568fa [Core] Remove unused variable in arm simd and inline rotate function' 2025-04-01 10:43:11 +13:00
Nic Barker
b4dc02c73a [Core] Fix a bug with how element string ids were stored when using Clay_Hovered 2025-04-01 10:40:04 +13:00
Nic Barker
3f635cdd79 [Renderers/Raylib] Fix FLAG_HIGHDPI causing window resize to break 2025-04-01 10:31:40 +13:00
Nic Barker
1204ac400b [Compilers] Fix implicit typecast in simd hash function
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-28 11:52:20 +13:00
Nic Barker
6a7ce77024 [Core] Fix implicit simd typecast on arm architectures 2025-03-28 11:47:57 +13:00
Piggybank Studios
7c9506bc31
[Core] Fix CLAY__ELEMENT_DEFINITION_LATCH overflow in CLAY macro if 256 loops end at the same time
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-27 10:14:17 +13:00
Nic Barker
08e4c5b198 [Core] Fix a bug where ID aliases werent copied on hash collision
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-26 09:35:15 +13:00
ellie-but-backwards
b1c72a0647
[Bindings/Odin] Remove field hashStringContents in odin bindings (#350) 2025-03-26 09:21:35 +13:00
Igor Karatayev
aee4baee1c
[Core] Guard against hashmap item null dereference (#338) 2025-03-26 09:19:50 +13:00
Nic Barker
47d1d84bc8
[Core] Switch text content hashing to default behaviour (#335)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-25 10:13:04 +13:00
Nic Barker
ad49977f1b [Core] Apply minimum width for single words and fix some minimum sizing bugs
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-21 11:22:48 +13:00
Leo Zurbriggen
61490e4557
[Bindings/Odin] expose _OpenElement and _CloseElement (#301)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-21 09:25:50 +13:00
Nic Barker
982ade4cf9 [Compilers] Add a dummy function to suppress unused variable warning in GCC
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-18 11:21:23 +13:00
Nic Barker
d5af2c3dc0
[Renderers/SDL2] Added explicit include of math.h in SDL2 renderer 2025-03-18 11:13:46 +13:00
Nic Barker
2677bec854 [Housekeeping] Revert previous commit to allow proper PR attribution 2025-03-18 11:12:21 +13:00
Nic Barker
05ac2810d8 [Renderers/SDL2] Added explicit include of math.h in SDL2 renderer 2025-03-18 11:10:53 +13:00
Nic Barker
1f8cab8d72 [Core] Fix a bug where floating elements could be clipped incorrectly 2025-03-18 11:05:06 +13:00
Emerald-Ruby
6186596b41
math.h include missing cause lots of warning logs 2025-03-15 21:51:36 +00:00
Nic Barker
a7d46629b1
[Renderers/SDL2] Fix rounded corner border index
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-13 09:52:16 +13:00
Nic Barker
bee93bc7ba
[Renderers/Raylib] Reuse memory in raylib renderer for temporary string allocations 2025-03-13 09:51:44 +13:00
Nic Barker
39fdd0e906
[Compilers] Fix integer truncation warnings with explicit casts 2025-03-13 09:40:31 +13:00
Nic Barker
008d4d2519
[Renderers/win32_gdi] Create initial WinGDI renderer 2025-03-13 09:27:44 +13:00
Nic Barker
3e39e444db Update README 2025-03-13 09:21:09 +13:00
Nic Barker
8a57153700 [Bindings/Odin] Add support for local ids to odin bindings 2025-03-13 09:08:20 +13:00
Nic Barker
09d581a523 [Bindings/Odin] Fix bad data type in odin bindings for floating config 2025-03-13 09:02:17 +13:00
Nic Barker
f824ddfd25
Merge pull request #320 from shakkar23/patch-1
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
[Renderers/SDL2] Enable live resizing of layout during window resize in SDL2
2025-03-11 10:29:34 +13:00
Nic Barker
82bb48a235
Merge pull request #300 from joshuahhh/patch-1
[Documentation] Update README.md: it's gotten bigger
2025-03-11 09:50:28 +13:00
Nic Barker
c06e01c1af
Merge pull request #319 from emoon/pass-declaration-by-pointer
Support passing declaration by pointer as well
2025-03-11 09:39:40 +13:00
hailey
6567f85eb3 Updated rectangle border rendering 2025-03-10 10:29:55 -05:00
hailey
a92ec772e1 [Renderers/Win32_GDI] first pass, fixed build errors and added build script 2025-03-10 09:33:12 -05:00
__hexmaster111
a782df73a1
Added win32 samples (first pass) 2025-03-10 09:23:36 -05:00
__hexmaster111
3a9172ec4c
Merge branch 'nicbarker:main' into main 2025-03-10 09:12:52 -05:00
Nic Barker
fabdad43f6 [Documentation] Update internal version number to 0.13 in clay.h
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-10 14:39:18 +13:00
Jesus Coca
e856136a8e
add resizing while the window is being resized 2025-03-08 17:37:02 -08:00
Daniel Collin
19a27b39f2
[Compilers] Fixed SIMD related compile error on some ARM compilers (#316)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-09 10:28:09 +13:00
Daniel Collin
33b8e76903 Support passing declaration by pointer as well 2025-03-08 15:17:36 +01:00
Johann Muszynski
ad4d00be33 Fix integer truncation warnings with explicit casts 2025-03-08 14:53:30 +02:00
Nic Barker
22e8cc318c [Bindings/Odin] Update odin bindings for text config userdata pointer
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-08 11:08:04 +13:00
Michael Savage
8e6640f7a2
[Core] Add a userData pointer to Clay_TextElementConfig (#274) 2025-03-08 11:01:26 +13:00
Ethan McCue
4f8957d5d2
[Documentation] Fix typo (#315)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-07 21:45:27 +13:00
Nic Barker
5009146c65 [Bindings/Odin] Recompile odin bindings with -O3
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-05 15:11:11 +13:00
Rico P
865b06d386
[Documentation] fix example in README (#307)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-05 10:14:37 +13:00
__hexmaster111
12319fc240
Updated measure text to support the defualt raylib font if the user spesfied font failed to load. (#305) 2025-03-05 10:13:42 +13:00
Laytan
2d7d5bc082
[Bindings/Odin] fix CreateArenaWithCapacityAndMemory capacity type (#306) 2025-03-05 09:49:53 +13:00
__hexmaster111
cf97539612
Update main.c 2025-03-04 05:57:30 -06:00
__hexmaster111
c49593f1d3
Update main.c 2025-03-04 05:56:20 -06:00
__hexmaster111
c7703b7a50
updated examples to call close 2025-03-04 05:55:30 -06:00
Joshua Horowitz
adc31f82e8
Update README.md: it's gotten bigger 2025-03-03 21:38:25 -08:00
__hexmaster111
ad363f986c
Added missing CloseWindow() call for raylib as well 2025-03-03 16:08:34 -06:00
__hexmaster111
3612431e82
[Raylib Render] Dont call malloc and Free every frame so much 2025-03-03 16:04:07 -06:00
Nic Barker
02bce89d17
[Core] Improve & streamline grow / shrink handling (#296)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-04 10:56:38 +13:00
FintasticMan
b5b086af13
[Macros] Add versions of the CLAY_ID macros that take Clay_String (#285) 2025-03-04 10:30:53 +13:00
mizmar
375501fb89
[Renderers/SDL2] Fix rounded corner border index 2025-03-03 18:36:04 +01:00
Nic Barker
5571c00a21 [Core] Convert capacity from uint32_t to size_t in Clay_CreateArenaWithCapacityAndMemory
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-03 11:36:12 +13:00
Joram Vandemoortele
4ee501019c
[Compilers] Added DLL macro to support .dll building (#278)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-02-26 15:37:51 +13:00
Nic Barker
1fa8684e47 [Core] Fix bug where hover state didnt take clip rectangles into account
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-02-25 08:55:14 +13:00
Benjamin Block
feead45f3e
[Documentation] Update README.md for Odin bindings to reflect the latest API changes. (#281)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-02-24 08:52:41 +13:00
mizmar
766325c395
[Core] Fix inverted condition for setting booleanWarnings.maxTextMeasureCacheExceeded (#275)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-02-20 09:22:35 +13:00
Alex Pedley
5afdf3f8c9
[Core] Make fakeContext use correct value from currentContext (#269) 2025-02-20 09:21:14 +13:00
Nic Barker
a60b977946 [Core] Fix a bug where floating elements would be incorrectly configured
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-02-18 09:41:06 +13:00
Laytan
20340f6544
[Bindings/Odin] add missing bindings, fix binding, improve ergonomics of userdata, conform to stricter style flags (#270)
Co-authored-by: Nic Barker <contact+github@nicbarker.com>
Co-authored-by: Courtney Strachan <courtney.strachan@gmail.com>
2025-02-18 09:16:31 +13:00
Timothy Hoyt
ee99e5f0f2
[Renderers/SDL2] Opengl, antialiasing, vsync, alpha blending (#264)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-02-17 09:15:58 +13:00
Nic Barker
256fc32549 [Documentation] Update README.md to include docs on Clay_GetElementData() 2025-02-17 09:12:11 +13:00
Thomas Anderson
28a8f59733
[Renderers/Raylib] Convert Image usage to Texture (#266) 2025-02-17 08:56:26 +13:00
Timothy Hoyt
47c8e9178e
[Renderers/SDL2] Make SDL_RenderCornerBorder static (#263) 2025-02-17 08:49:05 +13:00
irfan-zahir
a62ee15758
[Renderers/SDL3] Enable sdl3 alpha blending (#261) 2025-02-17 08:48:19 +13:00
Nic Barker
c73dffbb6f
[Github] Create FUNDING.yml
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-02-14 10:17:29 +13:00
Timothy Hoyt
eb553962e8
[Renderers/SDL2] Added rounded corner borders and fixed other issues (#258) 2025-02-14 10:14:11 +13:00
Nic Barker
d9e02ab1d3 [Core] Fix aspect ratio scaling of images when only one sizing axis was specified 2025-02-14 10:05:16 +13:00
tomat
bc2548e3ec
[Renderers/SDL3] Add image rendering and scissor support to SDL3 renderer (#246)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-02-13 10:02:06 +13:00
Julio Ernesto Rodríguez Cabañas
eeb4520f48
[Renderers/SDL3] Use text engine to render text on the SDL3 renderer (#256) 2025-02-13 09:19:36 +13:00
42 changed files with 2959 additions and 675 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [nicbarker]

View File

@ -53,7 +53,7 @@ jobs:
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache
uses: actions/cache@v4.0.2
uses: actions/cache@v4.2.0
with:
# A list of files, directories, and wildcard patterns to cache and restore
path: "/home/runner/work/clay/clay/build/_deps"

View File

@ -9,6 +9,8 @@ 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)
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
@ -36,5 +38,18 @@ endif ()
if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES))
add_subdirectory("examples/SDL3-simple-demo")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES)
add_subdirectory("examples/sokol-video-demo")
add_subdirectory("examples/sokol-corner-radius")
endif()
if(WIN32) # Build only for Win or Wine
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES)
add_subdirectory("examples/win32_gdi")
endif()
endif()
# add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
#add_library(${PROJECT_NAME} INTERFACE)
#target_include_directories(${PROJECT_NAME} INTERFACE .)

106
README.md
View File

@ -4,7 +4,7 @@
### Major Features
- Microsecond layout performance
- Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling
- Single ~2k LOC **clay.h** file with **zero** dependencies (including no standard library)
- Single ~4k LOC **clay.h** file with **zero** dependencies (including no standard library)
- Wasm support: compile with clang to a 15kb uncompressed **.wasm** file for use in the browser
- Static arena based memory use with no malloc / free, and low total memory overhead (e.g. ~3.5mb for 8192 layout elements).
- React-like nested declarative syntax
@ -89,7 +89,7 @@ int main() {
CLAY({
.id = CLAY_ID("SideBar"),
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 },
.backgroundColor = COLOR_LIGHT }
.backgroundColor = COLOR_LIGHT
}) {
CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {}
@ -474,6 +474,7 @@ Clay supports C preprocessor directives to modulate functionality at compile tim
The supported directives are:
- `CLAY_WASM` - Required when targeting Web Assembly.
- `CLAY_DLL` - Required when creating a .Dll file.
### Bindings for non C
@ -722,6 +723,15 @@ Returns [Clay_ScrollContainerData](#clay_scrollcontainerdata) for the scroll con
---
### Clay_GetElementData
`Clay_ElementData Clay_GetElementData(Clay_ElementId id)`
Returns [Clay_ElementData](#clay_elementdata) for the element matching the provided ID.
Used to retrieve information about elements such as their final calculated bounding box.
---
### Clay_GetElementId
`Clay_ElementId Clay_GetElementId(Clay_String idString)`
@ -887,18 +897,12 @@ Element is subject to [culling](#visibility-culling). Otherwise, multiple `Clay_
### CLAY_ID
**Usage**
`CLAY(CLAY_ID(char* idString)) {}`
**Lifecycle**
`Clay_BeginLayout()` -> `CLAY(` -> `CLAY_ID()` -> `)` -> `Clay_EndLayout()`
**Notes**
`Clay_ElementId CLAY_ID(STRING_LITERAL idString)`
**CLAY_ID()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID](#clay_sid).
To regenerate the same ID outside of layout declaration when using utility functions such as [Clay_PointerOver](#clay_pointerover), use the [Clay_GetElementId](#clay_getelementid) function.
**Examples**
@ -921,11 +925,31 @@ if (buttonIsHovered && leftMouseButtonPressed) {
---
### CLAY_SID()
`Clay_ElementId CLAY_SID(Clay_String idString)`
A version of [CLAY_ID](#clay_id) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
### CLAY_IDI()
`Clay_ElementId CLAY_IDI(char *label, int32_t index)`
`Clay_ElementId CLAY_IDI(STRING_LITERAL idString, 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.
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.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI](#clay_sidi).
---
### CLAY_SIDI()
`Clay_ElementId CLAY_SIDI(Clay_String idString, int32_t index)`
A version of [CLAY_IDI](#clay_idi) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
@ -933,7 +957,7 @@ An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_ele
**Usage**
`CLAY(CLAY_ID_LOCAL(char* idString)) {}`
`Clay_ElementId CLAY_ID_LOCAL(STRING_LITERAL idString)`
**Lifecycle**
@ -947,6 +971,8 @@ Unlike [CLAY_ID](#clay_id) which needs to be globally unique, a local ID is base
As a result, local id is suitable for use in reusable components and loops.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID_LOCAL](#clay_sid_local).
**Examples**
```C
@ -966,11 +992,31 @@ for (int i = 0; i < headerButtons.length; i++) {
---
### CLAY_SID_LOCAL()
`Clay_ElementId CLAY_SID_LOCAL(Clay_String idString)`
A version of [CLAY_ID_LOCAL](#clay_id_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
### CLAY_IDI_LOCAL()
`Clay_ElementId CLAY_IDI_LOCAL(char *label, int32_t index)`
`Clay_ElementId CLAY_IDI_LOCAL(STRING_LITERAL idString, 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.
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.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI_LOCAL](#clay_sidi_local).
---
### CLAY_SIDI_LOCAL()
`Clay_ElementId CLAY_SIDI_LOCAL(Clay_String idString, int32_t index)`
A version of [CLAY_IDI_LOCAL](#clay_idi_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
@ -1240,7 +1286,7 @@ Used to perform **aspect ratio scaling** on the image element. As of this versio
```C
// Load an image somewhere in your code
Image profilePicture = LoadImage("profilePicture.png");
YourImage profilePicture = LoadYourImage("profilePicture.png");
// Note that when rendering, .imageData will be void* type.
CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } } }) {}
```
@ -1249,7 +1295,7 @@ CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } }
```C
// Load an image somewhere in your code
Image profilePicture = LoadImage("profilePicture.png");
YourImage profilePicture = LoadYourImage("profilePicture.png");
// Declare a reusable image config
Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture, .sourceDimensions = {60, 60} };
// Declare an image element using a reusable config
@ -1257,7 +1303,7 @@ CLAY({ .image = imageConfig }) {}
// Declare an image element using an inline config
CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {}
// Rendering example
Image *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
```
**Rendering**
@ -1967,16 +2013,34 @@ typedef union {
### Clay_ScrollContainerData
```C
typedef struct
{
// Data representing the current internal state of a scrolling element.
typedef struct {
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
Clay_Vector2 *scrollPosition;
// The bounding box of the scroll element.
Clay_Dimensions scrollContainerDimensions;
// The outer dimensions of the inner scroll container content, including the padding of the parent scroll container.
Clay_Dimensions contentDimensions;
// The config that was originally passed to the scroll element.
Clay_ScrollElementConfig config;
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
bool found;
} Clay_ScrollContainerData;
```
### Clay_ElementData
```C
// Bounding box and other data for a specific UI element.
typedef struct {
// The rectangle that encloses this UI element, with the position relative to the root of the layout.
Clay_BoundingBox boundingBox;
// Indicates whether an actual Element matched the provided ID or if the default struct was returned.
bool found;
} Clay_ElementData;
```
**Fields**
**`.scrollPosition`** - `Clay_Vector2 *`

View File

@ -1,6 +1,6 @@
### Odin Language Bindings
This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [clay website](https://nicbarker.com/clay) in Odin.
This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [Clay website](https://nicbarker.com/clay) in Odin.
Special thanks to
@ -8,21 +8,21 @@ Special thanks to
- [Dudejoe870](https://github.com/Dudejoe870)
- MrStevns from the Odin Discord server
If you haven't taken a look at the [full documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using clay in Odin specifically.
The **most notable difference** between the C API and the Odin bindings is the use of if statements to create the scope for declaring child elements. When using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros):
If you haven't taken a look at the [full documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using Clay in Odin specifically.
The **most notable difference** between the C API and the Odin bindings is the use of `if` statements to create the scope for declaring child elements, when using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros):
```C
// C form of element macros
CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
// Child elements here
});
// Define an element with 16px of x and y padding
CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
// Child elements here
}
```
```Odin
// Odin form of element macros
if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BOTTOM, sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, padding = {16, 16} }), clay.RectangleConfig({ color = COLOR_LIGHT })) {
// Child elements here
if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) {
// Child elements here
}
```
@ -34,111 +34,170 @@ if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BO
import clay "clay-odin"
```
2. Ask clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(arena)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
2. Ask Clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(clay.Arena, clay.Dimensions, clay.ErrorHandler)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
```Odin
minMemorySize: u32 = clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena)
error_handler :: proc "c" (errorData: clay.ErrorData) {
// Do something with the error data.
}
min_memory_size: u32 = clay.MinMemorySize()
memory := make([^]u8, min_memory_size)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory)
clay.Initialize(arena, { width = 1080, height = 720 }, { handler = error_handler })
```
3. Provide a `measureText(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that clay can measure and wrap text.
3. Provide a `measure_text(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that Clay can measure and wrap text.
```Odin
// Example measure text function
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
measure_text :: proc "c" (
text: clay.StringSlice,
config: ^clay.TextElementConfig,
userData: rawptr,
) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc..
// Note: clay.String->chars is not guaranteed to be null terminated
return {
width = f32(text.length * i32(config.fontSize)),
height = f32(config.fontSize),
}
}
// Tell clay how to measure text
clay.SetMeasureTextFunction(measureText)
```
4. **Optional** - Call [clay.SetPointerPosition(pointerPosition)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerposition) if you want to use mouse interactions.
// Tell clay how to measure text
clay.SetMeasureTextFunction(measure_text, nil)
```
4. **Optional** - Call [clay.SetPointerState(pointerPosition, isPointerDown)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerstate) if you want to use mouse interactions.
```Odin
// Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY })
clay.SetPointerState(
clay.Vector2 { mouse_pos_x, mouse_pos_y },
is_mouse_down,
)
```
5. Call [clay.BeginLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
```Odin
// Define some colors.
COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
COLOR_BLACK :: clay.Color{0, 0, 0, 255}
// Layout config is just a struct that can be declared statically, or inline
sidebarItemLayout := clay.LayoutConfig {
sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)},
sidebar_item_layout := clay.LayoutConfig {
sizing = {
width = clay.SizingGrow({}),
height = clay.SizingFixed(50)
},
}
// Re-useable components are just normal functions
SidebarItemComponent :: proc(index: u32) {
if clay.Rectangle(clay.ID("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {}
// Re-useable components are just normal procs.
sidebar_item_component :: proc(index: u32) {
if clay.UI()({
id = clay.ID("SidebarBlob", index),
layout = sidebar_item_layout,
backgroundColor = COLOR_ORANGE,
}) {}
}
// An example function to begin the "root" of your layout tree
CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) {
clay.BeginLayout(windowWidth, windowHeight)
// An example function to create your layout tree
create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
// Begin constructing the layout.
clay.BeginLayout()
// An example of laying out a UI with a fixed width sidebar and flexible width main content
// NOTE: To create a scope for child components, the Odin api uses `if` with components that have children
if clay.Rectangle(
clay.ID("OuterContainer"),
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
clay.RectangleConfig({color = {250, 250, 255, 255}}),
) {
if clay.Rectangle(
clay.ID("SideBar"),
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingFixed(300), height = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
clay.RectangleConfig({color = COLOR_LIGHT}),
) {
if clay.Rectangle(
clay.ID("ProfilePictureOuter"),
clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}),
clay.RectangleConfig({color = COLOR_RED}),
) {
if clay.Image(
clay.ID("ProfilePicture"),
clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}),
clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}),
) {}
clay.Text(clay.ID("ProfileTitle"), "Clay - UI Library", clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}}))
// An example of laying out a UI with a fixed-width sidebar and flexible-width main content
// NOTE: To create a scope for child components, the Odin API uses `if` with components that have children
if clay.UI()({
id = clay.ID("OuterContainer"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = { 250, 250, 255, 255 },
}) {
if clay.UI()({
id = clay.ID("SideBar"),
layout = {
layoutDirection = .TopToBottom,
sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI()({
id = clay.ID("ProfilePictureOuter"),
layout = {
sizing = { width = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
childAlignment = { y = .Center },
},
backgroundColor = COLOR_RED,
cornerRadius = { 6, 6, 6, 6 },
}) {
if clay.UI()({
id = clay.ID("ProfilePicture"),
layout = {
sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) },
},
image = {
imageData = &profile_picture,
sourceDimensions = {
width = 60,
height = 60,
},
},
}) {}
clay.Text(
"Clay - UI Library",
clay.TextConfig({ textColor = COLOR_BLACK, fontSize = 16 }),
)
}
// Standard Odin code like loops etc work inside components
for i in 0..<10 {
SidebarItemComponent(i)
// Standard Odin code like loops, etc. work inside components.
// Here we render 5 sidebar items.
for i in u32(0)..<5 {
sidebar_item_component(i)
}
}
if clay.Rectangle(
clay.ID("MainContent"),
clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}),
clay.RectangleConfig({color = COLOR_LIGHT}),
) {}
if clay.UI()({
id = clay.ID("MainContent"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
},
backgroundColor = COLOR_LIGHT,
}) {}
}
// ...
// Returns a list of render commands
render_commands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout()
return render_commands
}
```
6. Call [clay.EndLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_endlayout) and process the resulting [clay.RenderCommandArray](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
6. Call your layout proc and process the resulting [clay.ClayArray(clay.RenderCommand)](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
```Odin
renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight)
render_commands := create_layout()
for i: u32 = 0; i < renderCommands.length; i += 1 {
renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i)
for i in 0..<i32(render_commands.length) {
render_command := clay.RenderCommandArray_Get(render_commands, i)
switch renderCommand.commandType {
switch render_command.commandType {
case .Rectangle:
DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color)
DrawRectangle(render_command.boundingBox, render_command.config.rectangleElementConfig.color)
// ... Implement handling of other command types
}
}
```
Please see the [full C documentation for clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_RECTANGLE` (C) -> `clay.Rectangle` (Odin)
Please see the [full C documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_ID` (C) -> `clay.ID` (Odin).

View File

@ -1,13 +1,13 @@
cp ../../clay.h clay.c;
# Intel Mac
clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-apple-darwin clay.c -fPIC && ar r clay-odin/macos/clay.a clay.o;
clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-apple-darwin clay.c -fPIC -O3 && ar r clay-odin/macos/clay.a clay.o;
# ARM Mac
clang -c -DCLAY_IMPLEMENTATION -g -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 -O3 && ar r clay-odin/macos-arm64/clay.a clay.o;
# x64 Windows
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/windows/clay.lib -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static clay.c;
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/windows/clay.lib -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static -O3 clay.c;
# Linux
clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -fPIC && ar r clay-odin/linux/clay.a clay.o;
clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -fPIC -O3 && ar r clay-odin/linux/clay.a clay.o;
# WASM
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static clay.c;
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c;
rm clay.o;
rm clay.c;

View File

@ -1,436 +1,470 @@
package clay
import "core:c"
import "core:strings"
when ODIN_OS == .Windows {
foreign import Clay "windows/clay.lib"
foreign import Clay "windows/clay.lib"
} else when ODIN_OS == .Linux {
foreign import Clay "linux/clay.a"
foreign import Clay "linux/clay.a"
} else when ODIN_OS == .Darwin {
when ODIN_ARCH == .arm64 {
foreign import Clay "macos-arm64/clay.a"
} else {
foreign import Clay "macos/clay.a"
}
when ODIN_ARCH == .arm64 {
foreign import Clay "macos-arm64/clay.a"
} else {
foreign import Clay "macos/clay.a"
}
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import Clay "wasm/clay.o"
foreign import Clay "wasm/clay.o"
}
String :: struct {
length: c.int32_t,
chars: [^]c.char,
isStaticallyAllocated: c.bool,
length: c.int32_t,
chars: [^]c.char,
}
StringSlice :: struct {
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
}
Vector2 :: [2]c.float
Dimensions :: struct {
width: c.float,
height: c.float,
width: c.float,
height: c.float,
}
Arena :: struct {
nextAllocation: uintptr,
capacity: uintptr,
memory: [^]c.char,
nextAllocation: uintptr,
capacity: c.size_t,
memory: [^]c.char,
}
BoundingBox :: struct {
x: c.float,
y: c.float,
width: c.float,
height: c.float,
x: c.float,
y: c.float,
width: c.float,
height: c.float,
}
Color :: [4]c.float
CornerRadius :: struct {
topLeft: c.float,
topRight: c.float,
bottomLeft: c.float,
bottomRight: c.float,
topLeft: c.float,
topRight: c.float,
bottomLeft: c.float,
bottomRight: c.float,
}
BorderData :: struct {
width: u32,
color: Color,
width: u32,
color: Color,
}
ElementId :: struct {
id: u32,
offset: u32,
baseId: u32,
stringId: String,
id: u32,
offset: u32,
baseId: u32,
stringId: String,
}
when ODIN_OS == .Windows {
EnumBackingType :: u32
EnumBackingType :: u32
} else {
EnumBackingType :: u8
EnumBackingType :: u8
}
RenderCommandType :: enum EnumBackingType {
None,
Rectangle,
Border,
Text,
Image,
ScissorStart,
ScissorEnd,
Custom,
None,
Rectangle,
Border,
Text,
Image,
ScissorStart,
ScissorEnd,
Custom,
}
RectangleElementConfig :: struct {
color: Color,
color: Color,
}
TextWrapMode :: enum EnumBackingType {
Words,
Newlines,
None,
Words,
Newlines,
None,
}
TextAlignment :: enum EnumBackingType {
Left,
Center,
Right,
Left,
Center,
Right,
}
TextElementConfig :: struct {
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
wrapMode: TextWrapMode,
textAlignment: TextAlignment,
hashStringContents: bool,
userData: rawptr,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
wrapMode: TextWrapMode,
textAlignment: TextAlignment,
}
ImageElementConfig :: struct {
imageData: rawptr,
sourceDimensions: Dimensions,
imageData: rawptr,
sourceDimensions: Dimensions,
}
CustomElementConfig :: struct {
customData: rawptr,
customData: rawptr,
}
BorderWidth :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
betweenChildren: u16,
left: u16,
right: u16,
top: u16,
bottom: u16,
betweenChildren: u16,
}
BorderElementConfig :: struct {
color: Color,
width: BorderWidth,
color: Color,
width: BorderWidth,
}
ScrollElementConfig :: struct {
horizontal: bool,
vertical: bool,
horizontal: bool,
vertical: bool,
}
FloatingAttachPointType :: enum EnumBackingType {
LeftTop,
LeftCenter,
LeftBottom,
CenterTop,
CenterCenter,
CenterBottom,
RightTop,
RightCenter,
RightBottom,
LeftTop,
LeftCenter,
LeftBottom,
CenterTop,
CenterCenter,
CenterBottom,
RightTop,
RightCenter,
RightBottom,
}
FloatingAttachPoints :: struct {
element: FloatingAttachPointType,
parent: FloatingAttachPointType,
element: FloatingAttachPointType,
parent: FloatingAttachPointType,
}
PointerCaptureMode :: enum EnumBackingType {
Capture,
Passthrough,
Capture,
Passthrough,
}
FloatingAttachToElement :: enum EnumBackingType {
None,
Parent,
ElementWithId,
Root,
None,
Parent,
ElementWithId,
Root,
}
FloatingElementConfig :: struct {
offset: Vector2,
expand: Dimensions,
parentId: u32,
zIndex: i32,
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement
offset: Vector2,
expand: Dimensions,
parentId: u32,
zIndex: i16,
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
}
TextRenderData :: struct {
stringContents: StringSlice,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
stringContents: StringSlice,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
}
RectangleRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
backgroundColor: Color,
cornerRadius: CornerRadius,
}
ImageRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
sourceDimensions: Dimensions,
imageData: rawptr,
backgroundColor: Color,
cornerRadius: CornerRadius,
sourceDimensions: Dimensions,
imageData: rawptr,
}
CustomRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
customData: rawptr,
backgroundColor: Color,
cornerRadius: CornerRadius,
customData: rawptr,
}
BorderRenderData :: struct {
color: Color,
cornerRadius: CornerRadius,
width: BorderWidth,
color: Color,
cornerRadius: CornerRadius,
width: BorderWidth,
}
RenderCommandData :: struct #raw_union {
rectangle: RectangleRenderData,
text: TextRenderData,
image: ImageRenderData,
custom: CustomRenderData,
border: BorderRenderData,
rectangle: RectangleRenderData,
text: TextRenderData,
image: ImageRenderData,
custom: CustomRenderData,
border: BorderRenderData,
}
RenderCommand :: struct {
boundingBox: BoundingBox,
renderData: RenderCommandData,
userData: rawptr,
id: u32,
zIndex: i16,
commandType: RenderCommandType,
boundingBox: BoundingBox,
renderData: RenderCommandData,
userData: rawptr,
id: u32,
zIndex: i16,
commandType: RenderCommandType,
}
ScrollContainerData :: struct {
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions,
config: ScrollElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool,
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions,
config: ScrollElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool,
}
ElementData :: struct {
boundingBox: BoundingBox,
found: bool,
}
PointerDataInteractionState :: enum EnumBackingType {
PressedThisFrame,
Pressed,
ReleasedThisFrame,
Released,
}
PointerData :: struct {
position: Vector2,
state: PointerDataInteractionState,
}
SizingType :: enum EnumBackingType {
Fit,
Grow,
Percent,
Fixed,
Fit,
Grow,
Percent,
Fixed,
}
SizingConstraintsMinMax :: struct {
min: c.float,
max: c.float,
min: c.float,
max: c.float,
}
SizingConstraints :: struct #raw_union {
sizeMinMax: SizingConstraintsMinMax,
sizePercent: c.float,
sizeMinMax: SizingConstraintsMinMax,
sizePercent: c.float,
}
SizingAxis :: struct {
// Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions
constraints: SizingConstraints,
type: SizingType,
// Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions
constraints: SizingConstraints,
type: SizingType,
}
Sizing :: struct {
width: SizingAxis,
height: SizingAxis,
width: SizingAxis,
height: SizingAxis,
}
Padding :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
left: u16,
right: u16,
top: u16,
bottom: u16,
}
LayoutDirection :: enum EnumBackingType {
LeftToRight,
TopToBottom,
LeftToRight,
TopToBottom,
}
LayoutAlignmentX :: enum EnumBackingType {
Left,
Right,
Center,
Left,
Right,
Center,
}
LayoutAlignmentY :: enum EnumBackingType {
Top,
Bottom,
Center,
Top,
Bottom,
Center,
}
ChildAlignment :: struct {
x: LayoutAlignmentX,
y: LayoutAlignmentY,
x: LayoutAlignmentX,
y: LayoutAlignmentY,
}
LayoutConfig :: struct {
sizing: Sizing,
padding: Padding,
childGap: u16,
childAlignment: ChildAlignment,
layoutDirection: LayoutDirection,
sizing: Sizing,
padding: Padding,
childGap: u16,
childAlignment: ChildAlignment,
layoutDirection: LayoutDirection,
}
ClayArray :: struct($type: typeid) {
capacity: i32,
length: i32,
internalArray: [^]type,
capacity: i32,
length: i32,
internalArray: [^]type,
}
ElementDeclaration :: struct {
id: ElementId,
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
scroll: ScrollElementConfig,
border: BorderElementConfig,
userData: rawptr
id: ElementId,
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
scroll: ScrollElementConfig,
border: BorderElementConfig,
userData: rawptr,
}
ErrorType :: enum EnumBackingType {
TextMeasurementFunctionNotProvided,
ArenaCapacityExceeded,
ElementsCapacityExceeded,
TextMeasurementCapacityExceeded,
DuplicateId,
FloatingContainerParentNotFound,
PercentageOver1,
InternalError,
TextMeasurementFunctionNotProvided,
ArenaCapacityExceeded,
ElementsCapacityExceeded,
TextMeasurementCapacityExceeded,
DuplicateId,
FloatingContainerParentNotFound,
PercentageOver1,
InternalError,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr
errorType: ErrorType,
errorText: String,
userData: rawptr,
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr
handler: proc "c" (errorData: ErrorData),
userData: rawptr,
}
Context :: struct {} // opaque structure, only use as a pointer
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---
EndLayout :: proc() -> ClayArray(RenderCommand) ---
Hovered :: proc() -> bool ---
PointerOver :: proc(id: ElementId) -> bool ---
GetElementId :: proc(id: String) -> ElementId ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
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) ---
GetCurrentContext :: proc() -> ^Context ---
SetCurrentContext :: proc(ctx: ^Context) ---
_OpenElement :: proc() ---
_CloseElement :: proc() ---
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context ---
GetCurrentContext :: proc() -> ^Context ---
SetCurrentContext :: proc(ctx: ^Context) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---
EndLayout :: proc() -> ClayArray(RenderCommand) ---
GetElementId :: proc(id: String) -> ElementId ---
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
GetElementData :: proc(id: ElementId) -> ElementData ---
Hovered :: proc() -> bool ---
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
PointerOver :: proc(id: ElementId) -> bool ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
SetDebugModeEnabled :: proc(enabled: bool) ---
IsDebugModeEnabled :: proc() -> bool ---
SetCullingEnabled :: proc(enabled: bool) ---
GetMaxElementCount :: proc() -> i32 ---
SetMaxElementCount :: proc(maxElementCount: i32) ---
GetMaxMeasureTextCacheWordCount :: proc() -> i32 ---
SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) ---
ResetMeasureTextCache :: proc() ---
}
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
_OpenElement :: proc() ---
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_CloseElement :: proc() ---
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
_HashString :: proc(toHash: String, index: u32, seed: u32) -> ElementId ---
_GetOpenLayoutElementId :: proc() -> u32 ---
}
ClayOpenElement :: struct {
configure: proc (config: ElementDeclaration) -> bool
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_HashString :: proc(key: String, offset: u32, seed: u32) -> ElementId ---
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
_GetParentElementId :: proc() -> u32 ---
}
ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool {
_ConfigureOpenElement(config)
return true;
_ConfigureOpenElement(config)
return true
}
@(deferred_none = _CloseElement)
UI :: proc() -> ClayOpenElement {
_OpenElement()
return { configure = ConfigureOpenElement }
UI :: proc() -> proc (config: ElementDeclaration) -> bool {
_OpenElement()
return ConfigureOpenElement
}
Text :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
Text :: proc($text: string, config: ^TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
return _StoreTextElementConfig(config)
return _StoreTextElementConfig(config)
}
PaddingAll :: proc(allPadding: u16) -> Padding {
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
}
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius}
return CornerRadius{radius, radius, radius, radius}
}
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
}
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
}
SizingFixed :: proc(size: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
}
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}}
return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}}
}
MakeString :: proc(label: string) -> String {
return String{chars = raw_data(label), length = cast(c.int)len(label)}
return String{chars = raw_data(label), length = cast(c.int)len(label)}
}
ID :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index, 0)
return _HashString(MakeString(label), index, 0)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index, _GetParentElementId())
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -62,14 +62,14 @@ border2pxRed := clay.BorderElementConfig {
color = COLOR_RED
}
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) {
if clay.UI().configure({
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.UI()({
id = clay.ID("HeroBlob", index),
layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
border = border2pxRed,
cornerRadius = clay.CornerRadiusAll(10)
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("CheckImage", index),
layout = { sizing = { width = clay.SizingFixed(32) } },
image = { imageData = image, sourceDimensions = { 128, 128 } },
@ -79,27 +79,27 @@ LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Colo
}
LandingPageDesktop :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("LandingPage1Desktop"),
layout = { sizing = { width = clay.SizingGrow({ }), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("LandingPage1"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
}) {
if clay.UI().configure({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
if clay.UI()({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 56, fontId = FONT_ID_TITLE_56, textColor = COLOR_RED}),
)
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}),
)
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("HeroImageOuter"),
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingPercent(0.45) }, childAlignment = { x = .Center }, childGap = 16 },
}) {
@ -114,7 +114,7 @@ LandingPageDesktop :: proc() {
}
LandingPageMobile :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("LandingPage1Mobile"),
layout = {
layoutDirection = .TopToBottom,
@ -124,18 +124,18 @@ LandingPageMobile :: proc() {
childGap = 32,
},
}) {
if clay.UI().configure({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingGrow({ }) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
if clay.UI()({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingGrow({ }) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}),
)
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}),
)
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("HeroImageOuter"),
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) }, childAlignment = { x = .Center }, childGap = 16 },
}) {
@ -150,17 +150,17 @@ LandingPageMobile :: proc() {
FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED})
if clay.UI().configure({
if clay.UI()({
id = clay.ID("HFileBoxOuter"),
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
}) {
if clay.UI().configure({ id = clay.ID("HFileIncludeOuter"), layout = { padding = { 8, 8, 4, 4 } }, backgroundColor = COLOR_RED, cornerRadius = clay.CornerRadiusAll(8) }) {
if clay.UI()({ id = clay.ID("HFileIncludeOuter"), layout = { padding = { 8, 8, 4, 4 } }, backgroundColor = COLOR_RED, cornerRadius = clay.CornerRadiusAll(8) }) {
clay.Text("#include clay.h", clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_LIGHT}))
}
clay.Text("~2000 lines of C99.", textConfig)
clay.Text("Zero dependencies, including no C standard library.", textConfig)
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("BringYourOwnRendererOuter"),
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
}) {
@ -171,8 +171,8 @@ FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
}
FeatureBlocksDesktop :: proc() {
if clay.UI().configure({ id = clay.ID("FeatureBlocksOuter"), layout = { sizing = { width = clay.SizingGrow({}) } } }) {
if clay.UI().configure({
if clay.UI()({ id = clay.ID("FeatureBlocksOuter"), layout = { sizing = { width = clay.SizingGrow({}) } } }) {
if clay.UI()({
id = clay.ID("FeatureBlocksInner"),
layout = { sizing = { width = clay.SizingGrow({ }) }, childAlignment = { y = .Center } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
@ -183,7 +183,7 @@ FeatureBlocksDesktop :: proc() {
}
FeatureBlocksMobile :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("FeatureBlocksInner"),
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
@ -193,9 +193,9 @@ FeatureBlocksMobile :: proc() {
}
DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI().configure({ id = clay.ID("SyntaxPageLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
if clay.UI()({ id = clay.ID("SyntaxPageLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("Declarative Syntax", clay.TextConfig(titleTextConfig))
if clay.UI().configure({ id = clay.ID("SyntaxSpacer"), layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
if clay.UI()({ id = clay.ID("SyntaxSpacer"), layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
clay.Text(
"Flexible and readable declarative syntax with nested UI element hierarchies.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
@ -209,8 +209,8 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
)
}
if clay.UI().configure({ id = clay.ID("SyntaxPageRightImage"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI().configure({
if clay.UI()({ id = clay.ID("SyntaxPageRightImage"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI()({
id = clay.ID("SyntaxPageRightImageInner"),
layout = { sizing = { width = clay.SizingGrow({ max = 568 }) } },
image = { imageData = &syntaxImage, sourceDimensions = { 1136, 1194 } },
@ -219,11 +219,11 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
}
DeclarativeSyntaxPageDesktop :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("SyntaxPageDesktop"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("SyntaxPage"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = border2pxRed,
@ -234,7 +234,7 @@ DeclarativeSyntaxPageDesktop :: proc() {
}
DeclarativeSyntaxPageMobile :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("SyntaxPageMobile"),
layout = {
layoutDirection = .TopToBottom,
@ -252,12 +252,12 @@ ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color {
return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount}
}
LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
LOREM_IPSUM_TEXT :: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI().configure({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
if clay.UI()({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("High Performance", clay.TextConfig(titleTextConfig))
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } }}) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } }}) {}
clay.Text(
"Fast enough to recompute your entire UI every frame.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
@ -271,20 +271,20 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
)
}
if clay.UI().configure({ id = clay.ID("PerformanceRightImageOuter"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI().configure({
if clay.UI()({ id = clay.ID("PerformanceRightImageOuter"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI()({
id = clay.ID("PerformanceRightBorder"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(400) } },
border = { COLOR_LIGHT, {2, 2, 2, 2, 2} },
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("AnimationDemoContainerLeft"),
layout = { sizing = { clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue),
}) {
clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("AnimationDemoContainerRight"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue),
@ -296,7 +296,7 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
}
HighPerformancePageDesktop :: proc(lerpValue: f32) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("PerformanceDesktop"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { 82, 82, 32, 32 }, childGap = 64 },
backgroundColor = COLOR_RED,
@ -306,7 +306,7 @@ HighPerformancePageDesktop :: proc(lerpValue: f32) {
}
HighPerformancePageMobile :: proc(lerpValue: f32) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("PerformanceMobile"),
layout = {
layoutDirection = .TopToBottom,
@ -321,8 +321,8 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
}
}
RendererButtonActive :: proc(index: i32, text: string) {
if clay.UI().configure({
RendererButtonActive :: proc(index: i32, $text: string) {
if clay.UI()({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_RED,
cornerRadius = clay.CornerRadiusAll(10)
@ -331,9 +331,9 @@ RendererButtonActive :: proc(index: i32, text: string) {
}
}
RendererButtonInactive :: proc(index: u32, text: string) {
if clay.UI().configure({ border = border2pxRed }) {
if clay.UI().configure({
RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.UI()({ border = border2pxRed }) {
if clay.UI()({
id = clay.ID("RendererButtonInactiveInner", index),
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_LIGHT,
@ -345,9 +345,9 @@ RendererButtonInactive :: proc(index: u32, text: string) {
}
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI().configure({ id = clay.ID("RendererLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
if clay.UI()({ id = clay.ID("RendererLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig))
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
clay.Text(
"Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
@ -361,22 +361,22 @@ RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
)
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("RendererRightText"),
layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center }, layoutDirection = .TopToBottom, childGap = 16 },
}) {
clay.Text("Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}))
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
RendererButtonActive(0, "Raylib Renderer")
}
}
RendererPageDesktop :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("RendererPageDesktop"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("RendererPage"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
@ -387,7 +387,7 @@ RendererPageDesktop :: proc() {
}
RendererPageMobile :: proc() {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("RendererMobile"),
layout = {
layoutDirection = .TopToBottom,
@ -414,27 +414,27 @@ animationLerpValue: f32 = -1.0
createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
mobileScreen := windowWidth < 750
clay.BeginLayout()
if clay.UI().configure({
if clay.UI()({
id = clay.ID("OuterContainer"),
layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) } },
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI().configure({
if clay.UI()({
id = clay.ID("Header"),
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(50) }, childAlignment = { y = .Center }, childGap = 24, padding = { left = 32, right = 32 } },
}) {
clay.Text("Clay", &headerTextConfig)
if clay.UI().configure({ layout = { sizing = { width = clay.SizingGrow({ }) } } }) {}
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ }) } } }) {}
if (!mobileScreen) {
if clay.UI().configure({ id = clay.ID("LinkExamplesOuter"), backgroundColor = {0, 0, 0, 0} }) {
if clay.UI()({ id = clay.ID("LinkExamplesOuter"), backgroundColor = {0, 0, 0, 0} }) {
clay.Text("Examples", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
if clay.UI().configure({ id = clay.ID("LinkDocsOuter"), backgroundColor = {0, 0, 0, 0} }) {
if clay.UI()({ id = clay.ID("LinkDocsOuter"), backgroundColor = {0, 0, 0, 0} }) {
clay.Text("Docs", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
}
if clay.UI().configure({
if clay.UI()({
id = clay.ID("LinkGithubOuter"),
layout = { padding = { 16, 16, 6, 6 } },
border = border2pxRed,
@ -444,12 +444,12 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
clay.Text("Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
}
if clay.UI().configure({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
if clay.UI().configure({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
if clay.UI().configure({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
if clay.UI().configure({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
if clay.UI().configure({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
if clay.UI().configure({
if clay.UI()({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
if clay.UI()({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
if clay.UI()({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
if clay.UI()({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
if clay.UI()({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
if clay.UI()({
id = clay.ID("ScrollContainerBackgroundRectangle"),
scroll = { vertical = true },
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, layoutDirection = clay.LayoutDirection.TopToBottom },
@ -489,13 +489,13 @@ errorHandler :: proc "c" (errorData: clay.ErrorData) {
}
main :: proc() {
minMemorySize: u32 = clay.MinMemorySize()
minMemorySize: c.size_t = cast(c.size_t)clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
clay.SetMeasureTextFunction(measureText, 0)
clay.SetMeasureTextFunction(measureText, nil)
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
raylib.SetTargetFPS(raylib.GetMonitorRefreshRate(0))
loadFont(FONT_ID_TITLE_56, 56, "resources/Calistoga-Regular.ttf")
@ -509,12 +509,12 @@ main :: proc() {
loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf")
syntaxImage = raylib.LoadTextureFromImage(raylib.LoadImage("resources/declarative.png"))
checkImage1 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_1.png"))
checkImage2 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_2.png"))
checkImage3 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_3.png"))
checkImage4 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_4.png"))
checkImage5 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_5.png"))
syntaxImage = raylib.LoadTexture("resources/declarative.png")
checkImage1 = raylib.LoadTexture("resources/check_1.png")
checkImage2 = raylib.LoadTexture("resources/check_2.png")
checkImage3 = raylib.LoadTexture("resources/check_3.png")
checkImage4 = raylib.LoadTexture("resources/check_4.png")
checkImage5 = raylib.LoadTexture("resources/check_5.png")
debugModeEnabled: bool = false

View File

@ -16,7 +16,7 @@ clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color {
raylibFonts := [10]RaylibFont{}
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: uintptr) -> clay.Dimensions {
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions {
// Measure string size for Font
textSize: clay.Dimensions = {0, 0}

559
clay.h
View File

@ -1,4 +1,4 @@
// VERSION: 0.12
// VERSION: 0.13
/*
NOTE: In order to use this library you must define
@ -42,6 +42,12 @@
#define CLAY_WASM_EXPORT(null)
#endif
#ifdef CLAY_DLL
#define CLAY_DLL_EXPORT __declspec(dllexport) __stdcall
#else
#define CLAY_DLL_EXPORT
#endif
// Public Macro API ------------------------
#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y))
@ -65,25 +71,41 @@
#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT })
// Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead.
#define CLAY_ID(label) CLAY_IDI(label, 0)
#define CLAY_IDI(label, index) Clay__HashString(CLAY_STRING(label), index, 0)
#define CLAY_SID(label) CLAY_SIDI(label, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead.
#define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index)
#define CLAY_SIDI(label, index) Clay__HashString(label, index, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead.
#define CLAY_ID_LOCAL(label) CLAY_IDI_LOCAL(label, 0)
#define CLAY_IDI_LOCAL(label, index) Clay__HashString(CLAY_STRING(label), index, Clay__GetParentElementId())
#define CLAY_SID_LOCAL(label) CLAY_SIDI_LOCAL(label, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead.
#define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index)
#define CLAY_SIDI_LOCAL(label, index) Clay__HashString(label, index, Clay__GetParentElementId())
#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0]))
#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "")
// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString)
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING_CONST(string) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
#define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
// GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout
// This is to suppress that warning
static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; }
// Publicly visible layout element macros -----------------------------------------------------
/* This macro looks scary on the surface, but is actually quite simple.
@ -114,7 +136,7 @@ static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
for ( \
CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \
CLAY__ELEMENT_DEFINITION_LATCH < 1; \
++CLAY__ELEMENT_DEFINITION_LATCH, Clay__CloseElement() \
CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \
)
// These macros exist to allow the CLAY() macro to be called both with an inline struct definition, such as
@ -163,6 +185,9 @@ extern "C" {
// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string,
// but it is also used to represent slices.
typedef struct {
// Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program.
// This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal.
bool isStaticallyAllocated;
int32_t length;
// The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory.
const char *chars;
@ -248,7 +273,7 @@ typedef CLAY_PACKED_ENUM {
CLAY_ALIGN_Y_TOP,
// Aligns child elements to the bottom of this element, offset by padding.width.bottom
CLAY_ALIGN_Y_BOTTOM,
// Aligns child elements vertiically to the center of this element
// Aligns child elements vertically to the center of this element
CLAY_ALIGN_Y_CENTER,
} Clay_LayoutAlignmentY;
@ -339,6 +364,8 @@ typedef CLAY_PACKED_ENUM {
// Controls various functionality related to text elements.
typedef struct {
// A pointer that will be transparently passed through to the resulting render command.
void *userData;
// The RGBA color of the font to render, conventionally specified as 0-255.
Clay_Color textColor;
// An integer transparently passed to Clay_MeasureText to identify the font to use.
@ -360,10 +387,6 @@ typedef struct {
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
Clay_TextAlignment textAlignment;
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
// long bodies of text.
bool hashStringContents;
} Clay_TextElementConfig;
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
@ -769,102 +792,102 @@ typedef struct {
// Public API functions ------------------------------------------
// Returns the size, in bytes, of the minimum amount of memory Clay requires to operate at its current settings.
uint32_t Clay_MinMemorySize(void);
CLAY_DLL_EXPORT uint32_t Clay_MinMemorySize(void);
// Creates an arena for clay to use for its internal allocations, given a certain capacity in bytes and a pointer to an allocation of at least that size.
// Intended to be used with Clay_MinMemorySize in the following way:
// uint32_t minMemoryRequired = Clay_MinMemorySize();
// Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(minMemoryRequired, malloc(minMemoryRequired));
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *memory);
CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory);
// Sets the state of the "pointer" (i.e. the mouse or touch) in Clay's internal data. Used for detecting and responding to mouse events in the debug view,
// as well as for Clay_Hovered() and scroll element handling.
void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown);
CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown);
// Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once.
// - arena can be created using Clay_CreateArenaWithCapacityAndMemory()
// - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout)
// - errorHandler is used by Clay to inform you if something has gone wrong in configuration or layout.
Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler);
CLAY_DLL_EXPORT Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler);
// Returns the Context that clay is currently using. Used when using multiple instances of clay simultaneously.
Clay_Context* Clay_GetCurrentContext(void);
CLAY_DLL_EXPORT Clay_Context* Clay_GetCurrentContext(void);
// Sets the context that clay will use to compute the layout.
// Used to restore a context saved from Clay_GetCurrentContext when using multiple instances of clay simultaneously.
void Clay_SetCurrentContext(Clay_Context* context);
CLAY_DLL_EXPORT void Clay_SetCurrentContext(Clay_Context* context);
// Updates the state of Clay's internal scroll data, updating scroll content positions if scrollDelta is non zero, and progressing momentum scrolling.
// - enableDragScrolling when set to true will enable mobile device like "touch drag" scroll of scroll containers, including momentum scrolling after the touch has ended.
// - scrollDelta is the amount to scroll this frame on each axis in pixels.
// - deltaTime is the time in seconds since the last "frame" (scroll update)
void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime);
CLAY_DLL_EXPORT void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime);
// Updates the layout dimensions in response to the window or outer container being resized.
void Clay_SetLayoutDimensions(Clay_Dimensions dimensions);
CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions);
// Called before starting any layout declarations.
void Clay_BeginLayout(void);
CLAY_DLL_EXPORT void Clay_BeginLayout(void);
// Called when all layout declarations are finished.
// Computes the layout and generates and returns the array of render commands to draw.
Clay_RenderCommandArray Clay_EndLayout(void);
CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(void);
// Calculates a hash ID from the given idString.
// Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used.
Clay_ElementId Clay_GetElementId(Clay_String idString);
CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString);
// Calculates a hash ID from the given idString and index.
// - index is used to avoid constructing dynamic ID strings in loops.
// Generally only used for dynamic strings when CLAY_IDI("stringLiteral", index) can't be used.
Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index);
CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index);
// Returns layout data such as the final calculated bounding box for an element with a given ID.
// The returned Clay_ElementData contains a `found` bool that will be true if an element with the provided ID was found.
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
Clay_ElementData Clay_GetElementData(Clay_ElementId id);
CLAY_DLL_EXPORT Clay_ElementData Clay_GetElementData(Clay_ElementId id);
// Returns true if the pointer position provided by Clay_SetPointerState is within the current element's bounding box.
// Works during element declaration, e.g. CLAY({ .backgroundColor = Clay_Hovered() ? BLUE : RED });
bool Clay_Hovered(void);
CLAY_DLL_EXPORT bool Clay_Hovered(void);
// Bind a callback that will be called when the pointer position provided by Clay_SetPointerState is within the current element's bounding box.
// - onHoverFunction is a function pointer to a user defined function.
// - userData is a pointer that will be transparently passed through when the onHoverFunction is called.
void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
CLAY_DLL_EXPORT void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box.
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
bool Clay_PointerOver(Clay_ElementId elementId);
CLAY_DLL_EXPORT bool Clay_PointerOver(Clay_ElementId elementId);
// Returns data representing the state of the scrolling element with the provided ID.
// The returned Clay_ScrollContainerData contains a `found` bool that will be true if a scroll element was found with the provided ID.
// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box.
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
CLAY_DLL_EXPORT Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
// Binds a callback function that Clay will call to determine the dimensions of a given string slice.
// - measureTextFunction is a user provided function that adheres to the interface Clay_Dimensions (Clay_StringSlice text, Clay_TextElementConfig *config, void *userData);
// - userData is a pointer that will be transparently passed through when the measureTextFunction is called.
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData);
CLAY_DLL_EXPORT void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData);
// Experimental - Used in cases where Clay needs to integrate with a system that manages its own scrolling containers externally.
// Please reach out if you plan to use this function, as it may be subject to change.
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData);
CLAY_DLL_EXPORT void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData);
// A bounds-checked "get" function for the Clay_RenderCommandArray returned from Clay_EndLayout().
Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
CLAY_DLL_EXPORT Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
// Enables and disables Clay's internal debug tools.
// This state is retained and does not need to be set each frame.
void Clay_SetDebugModeEnabled(bool enabled);
CLAY_DLL_EXPORT void Clay_SetDebugModeEnabled(bool enabled);
// Returns true if Clay's internal debug tools are currently enabled.
bool Clay_IsDebugModeEnabled(void);
CLAY_DLL_EXPORT bool Clay_IsDebugModeEnabled(void);
// Enables and disables visibility culling. By default, Clay will not generate render commands for elements whose bounding box is entirely outside the screen.
void Clay_SetCullingEnabled(bool enabled);
CLAY_DLL_EXPORT void Clay_SetCullingEnabled(bool enabled);
// Returns the maximum number of UI elements supported by Clay's current configuration.
int32_t Clay_GetMaxElementCount(void);
CLAY_DLL_EXPORT int32_t Clay_GetMaxElementCount(void);
// Modifies the maximum number of UI elements supported by Clay's current configuration.
// This may require reallocating additional memory, and re-calling Clay_Initialize();
void Clay_SetMaxElementCount(int32_t maxElementCount);
CLAY_DLL_EXPORT void Clay_SetMaxElementCount(int32_t maxElementCount);
// Returns the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
// This may require reallocating additional memory, and re-calling Clay_Initialize();
void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
// Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used.
// Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents
void Clay_ResetMeasureTextCache(void);
CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
// Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded.
CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void);
// Internal API functions required by macros ----------------------
void Clay__OpenElement(void);
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
void Clay__CloseElement(void);
Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config);
uint32_t Clay__GetParentElementId(void);
CLAY_DLL_EXPORT void Clay__OpenElement(void);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config);
CLAY_DLL_EXPORT void Clay__CloseElement(void);
CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config);
CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void);
extern Clay_Color Clay__debugViewHighlightColor;
extern uint32_t Clay__debugViewWidth;
@ -1131,6 +1154,7 @@ CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray)
typedef struct {
Clay_Dimensions unwrappedDimensions;
int32_t measuredWordsStartIndex;
float minWidth;
bool containsNewlines;
// Hash map data
uint32_t id;
@ -1322,26 +1346,136 @@ Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const ui
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
}
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
uintptr_t pointerAsNumber = (uintptr_t)text->chars;
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) {
return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r));
}
if (config->hashStringContents) {
uint32_t maxLengthToHash = CLAY__MIN(text->length, 256);
for (uint32_t i = 0; i < maxLengthToHash; i++) {
hash += text->chars[i];
hash += (hash << 10);
hash ^= (hash >> 6);
static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) {
*a = _mm_add_epi64(*a, *b);
*b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
__m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL);
__m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL);
__m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL);
__m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs
while (length > 0) {
__m128i msg;
if (length >= 16) {
msg = _mm_loadu_si128((const __m128i*)data);
data += 16;
length -= 16;
}
} else {
hash += pointerAsNumber;
else {
for (size_t i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
msg = _mm_loadu_si128((const __m128i*)overflowBuffer);
length = 0;
}
v0 = _mm_xor_si128(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
uint64_t result[2];
_mm_storeu_si128((__m128i*)result, v0);
return result[0] ^ result[1];
}
#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__)
static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) {
*a = vaddq_u64(*a, *b);
*b = veorq_u64(vorrq_u64(vshlq_n_u64(*b, 17), vshrq_n_u64(*b, 64 - 17)), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL);
uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL);
uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL);
uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[8] = { 0 };
while (length > 0) {
uint64x2_t msg;
if (length > 16) {
msg = vld1q_u64((const uint64_t*)data);
data += 16;
length -= 16;
}
else if (length > 8) {
msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0));
data += 8;
length -= 8;
}
else {
for (size_t i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
uint8x8_t lower = vld1_u8(overflowBuffer);
msg = vreinterpretq_u64_u8(vcombine_u8(lower, vdup_n_u8(0)));
length = 0;
}
v0 = veorq_u64(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
uint64_t result[2];
vst1q_u64(result, v0);
return result[0] ^ result[1];
}
#else
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
uint64_t hash = 0;
for (int32_t i = 0; i < length; i++) {
hash += data[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
return hash;
}
#endif
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
if (text->isStaticallyAllocated) {
hash += (uintptr_t)text->chars;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
} else {
hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX;
}
hash += config->fontId;
hash += (hash << 10);
@ -1351,18 +1485,10 @@ uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *con
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->lineHeight;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->letterSpacing;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->wrapMode;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
@ -1397,7 +1523,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
return &Clay__MeasureTextCacheItem_DEFAULT;
}
#endif
uint32_t id = Clay__HashTextWithConfig(text, config);
uint32_t id = Clay__HashStringContentsWithConfig(text, config);
uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32);
int32_t elementIndexPrevious = 0;
int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket];
@ -1443,7 +1569,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
measured = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, newItemIndex);
} else {
if (context->measureTextHashMapInternal.length == context->measureTextHashMapInternal.capacity - 1) {
if (context->booleanWarnings.maxTextMeasureCacheExceeded) {
if (!context->booleanWarnings.maxTextMeasureCacheExceeded) {
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
.errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED,
.errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."),
@ -1479,6 +1605,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
if (current == ' ' || current == '\n') {
int32_t length = end - start;
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData);
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
if (current == ' ') {
dimensions.width += spaceWidth;
@ -1504,6 +1631,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
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);
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
}
measuredWidth = CLAY__MAX(lineWidth, measuredWidth);
@ -1538,9 +1666,12 @@ Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Cl
item.nextIndex = hashItem->nextIndex;
if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element
hashItem->elementId = elementId; // Make sure to copy this across. If the stringId reference has changed, we should update the hash item to use the new one.
hashItem->idAlias = idAlias;
hashItem->generation = context->generation + 1;
hashItem->layoutElement = layoutElement;
hashItem->debugData->collision = false;
hashItem->onHoverFunction = NULL;
hashItem->hoverFunctionUserData = 0;
} else { // Multiple collisions this frame - two elements have the same ID
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
.errorType = CLAY_ERROR_TYPE_DUPLICATE_ID,
@ -1598,6 +1729,25 @@ bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConf
return false;
}
void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) {
for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) {
Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j);
if (config->type == CLAY__ELEMENT_CONFIG_TYPE_IMAGE) {
Clay_ImageElementConfig *imageConfig = config->config.imageElementConfig;
if (imageConfig->sourceDimensions.width == 0 || imageConfig->sourceDimensions.height == 0) {
break;
}
float aspect = imageConfig->sourceDimensions.width / imageConfig->sourceDimensions.height;
if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) {
layoutElement->dimensions.width = layoutElement->dimensions.height * aspect;
} else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) {
layoutElement->dimensions.height = layoutElement->dimensions.height * (1 / aspect);
}
break;
}
}
}
void Clay__CloseElement(void) {
Clay_Context* context = Clay_GetCurrentContext();
if (context->booleanWarnings.maxElementsExceeded) {
@ -1614,24 +1764,30 @@ void Clay__CloseElement(void) {
elementHasScrollVertical = config->config.scrollElementConfig->vertical;
context->openClipElementStack.length--;
break;
} else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) {
context->openClipElementStack.length--;
}
}
float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
// Attach children to the current open element
openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length];
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
openLayoutElement->dimensions.width = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->dimensions.width = leftRightPadding;
openLayoutElement->minDimensions.width = leftRightPadding;
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
openLayoutElement->dimensions.width += child->dimensions.width;
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (!elementHasScrollHorizontal) {
openLayoutElement->minDimensions.width += child->minDimensions.width;
}
if (!elementHasScrollVertical) {
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding);
}
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
}
@ -1640,18 +1796,19 @@ void Clay__CloseElement(void) {
openLayoutElement->minDimensions.width += childGap;
}
else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) {
openLayoutElement->dimensions.height = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->dimensions.height = topBottomPadding;
openLayoutElement->minDimensions.height = topBottomPadding;
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
openLayoutElement->dimensions.height += child->dimensions.height;
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (!elementHasScrollVertical) {
openLayoutElement->minDimensions.height += child->minDimensions.height;
}
if (!elementHasScrollHorizontal) {
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding);
}
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
}
@ -1684,6 +1841,8 @@ void Clay__CloseElement(void) {
openLayoutElement->dimensions.height = 0;
}
Clay__UpdateAspectRatioBox(openLayoutElement);
bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
// Close the currently open element
@ -1730,7 +1889,7 @@ bool Clay__MemCmp(const char *s1, const char *s2, int32_t length);
uint8x16_t v2 = vld1q_u8((const uint8_t *)s2);
// Compare vectors
if (vminvq_u32(vceqq_u8(v1, v2)) != 0xFFFFFFFF) { // If there's a difference
if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference
return false;
}
@ -1801,7 +1960,7 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig)
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height };
textElement->dimensions = textDimensions;
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->unwrappedDimensions.height, .height = textDimensions.height }; // TODO not sure this is the best way to decide min width for text
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height };
textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 });
textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) {
.length = 1,
@ -1820,62 +1979,62 @@ Clay_ElementId Clay__AttachId(Clay_ElementId elementId) {
uint32_t idAlias = openLayoutElement->id;
openLayoutElement->id = elementId.id;
Clay__AddHashMapItem(elementId, openLayoutElement, idAlias);
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
Clay__StringArray_Set(&context->layoutElementIdStrings, context->layoutElements.length - 1, elementId.stringId);
return elementId;
}
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) {
Clay_Context* context = Clay_GetCurrentContext();
Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement();
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration.layout);
if ((declaration.layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.width.size.percent > 1) || (declaration.layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.height.size.percent > 1)) {
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout);
if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) {
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
.errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1,
.errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."),
.userData = context->errorHandler.userData });
}
Clay_ElementId openLayoutElementId = declaration.id;
Clay_ElementId openLayoutElementId = declaration->id;
openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length];
Clay_SharedElementConfig *sharedConfig = NULL;
if (declaration.backgroundColor.a > 0) {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration.backgroundColor });
if (declaration->backgroundColor.a > 0) {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
if (!Clay__MemCmp((char *)(&declaration.cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
if (sharedConfig) {
sharedConfig->cornerRadius = declaration.cornerRadius;
sharedConfig->cornerRadius = declaration->cornerRadius;
} else {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration.cornerRadius });
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
}
if (declaration.userData != 0) {
if (declaration->userData != 0) {
if (sharedConfig) {
sharedConfig->userData = declaration.userData;
sharedConfig->userData = declaration->userData;
} else {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration.userData });
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
}
if (declaration.image.imageData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration.image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
if (declaration->image.imageData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
Clay__int32_tArray_Add(&context->imageElementPointers, context->layoutElements.length - 1);
}
if (declaration.floating.attachTo != CLAY_ATTACH_TO_NONE) {
Clay_FloatingElementConfig floatingConfig = declaration.floating;
if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) {
Clay_FloatingElementConfig floatingConfig = declaration->floating;
// This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here
Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2));
if (hierarchicalParent) {
uint32_t clipElementId = 0;
if (declaration.floating.attachTo == CLAY_ATTACH_TO_PARENT) {
if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) {
// Attach to the element's direct hierarchical parent
floatingConfig.parentId = hierarchicalParent->id;
if (context->openClipElementStack.length > 0) {
clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1);
}
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId);
if (!parentItem) {
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
@ -1883,25 +2042,28 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
.errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."),
.userData = context->errorHandler.userData });
} else {
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, parentItem->layoutElement - context->layoutElements.internalArray);
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray));
}
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ROOT) {
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) {
floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id;
}
if (!openLayoutElementId.id) {
openLayoutElementId = Clay__HashString(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
}
int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1);
Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId);
Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId);
Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) {
.layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1),
.parentId = floatingConfig.parentId,
.clipElementId = clipElementId,
.zIndex = floatingConfig.zIndex,
});
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(declaration.floating) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
}
}
if (declaration.custom.customData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration.custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
if (declaration->custom.customData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
}
if (openLayoutElementId.id != 0) {
@ -1910,8 +2072,8 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
openLayoutElementId = Clay__GenerateIdForAnonymousElement(openLayoutElement);
}
if (declaration.scroll.horizontal | declaration.scroll.vertical) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration.scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
if (declaration->scroll.horizontal | declaration->scroll.vertical) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration->scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id);
// Retrieve or create cached data to track scroll position across frames
Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL;
@ -1930,11 +2092,15 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData);
}
}
if (!Clay__MemCmp((char *)(&declaration.border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration.border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER);
if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER);
}
}
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
Clay__ConfigureOpenElementPtr(&declaration);
}
void Clay__InitializeEphemeralMemory(Clay_Context* context) {
int32_t maxElementCount = context->maxElementCount;
// Ephemeral Memory - reset every frame
@ -1991,54 +2157,11 @@ void Clay__InitializePersistentMemory(Clay_Context* context) {
context->arenaResetOffset = arena->nextAllocation;
}
void Clay__CompressChildrenAlongAxis(bool xAxis, float totalSizeToDistribute, Clay__int32_tArray resizableContainerBuffer) {
Clay_Context* context = Clay_GetCurrentContext();
Clay__int32_tArray largestContainers = context->openClipElementStack;
const float CLAY__EPSILON = 0.01;
while (totalSizeToDistribute > 0.1) {
largestContainers.length = 0;
float largestSize = 0;
float targetSize = 0;
for (int32_t i = 0; i < resizableContainerBuffer.length; ++i) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height;
if ((childSize - largestSize) < 0.1 && (childSize - largestSize) > -0.1) {
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
} else if (childSize > largestSize) {
targetSize = largestSize;
largestSize = childSize;
largestContainers.length = 0;
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
}
else if (childSize > targetSize) {
targetSize = childSize;
}
}
if (largestContainers.length == 0) {
return;
}
targetSize = CLAY__MAX(targetSize, (largestSize * largestContainers.length) - totalSizeToDistribute) / largestContainers.length;
for (int32_t childOffset = 0; childOffset < largestContainers.length; childOffset++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&largestContainers, childOffset);
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
float oldChildSize = *childSize;
*childSize = CLAY__MAX(childMinSize, targetSize);
totalSizeToDistribute -= (oldChildSize - *childSize);
if (*childSize == childMinSize) {
for (int32_t i = 0; i < resizableContainerBuffer.length; i++) {
if (Clay__int32_tArray_GetValue(&resizableContainerBuffer, i) == childIndex) {
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, i);
break;
}
}
}
}
}
bool Clay__FloatEqual(float left, float right) {
float subtracted = left - right;
return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON;
}
void Clay__SizeContainersAlongAxis(bool xAxis) {
@ -2076,7 +2199,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
int32_t growContainerCount = 0;
float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height;
float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom));
float innerContentSize = 0, growContainerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM);
resizableContainerBuffer.length = 0;
float parentChildGap = parentStyleConfig->childGap;
@ -2102,7 +2225,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
if (sizingAlongAxis) {
innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize);
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
growContainerContentSize += childSize;
growContainerCount++;
}
if (childOffset > 0) {
@ -2125,6 +2247,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
if (sizingAlongAxis) {
innerContentSize += *childSize;
}
Clay__UpdateAspectRatioBox(childElement);
}
}
@ -2140,25 +2263,83 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
}
}
// Scrolling containers preferentially compress before others
Clay__CompressChildrenAlongAxis(xAxis, -sizeToDistribute, resizableContainerBuffer);
while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) {
float largest = 0;
float secondLargest = 0;
float widthToAdd = sizeToDistribute;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
if (Clay__FloatEqual(childSize, largest)) { continue; }
if (childSize > largest) {
secondLargest = largest;
largest = childSize;
}
if (childSize < largest) {
secondLargest = CLAY__MAX(secondLargest, childSize);
widthToAdd = secondLargest - largest;
}
}
widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height;
float previousWidth = *childSize;
if (Clay__FloatEqual(*childSize, largest)) {
*childSize += widthToAdd;
if (*childSize <= minSize) {
*childSize = minSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
sizeToDistribute -= (*childSize - previousWidth);
}
}
}
// The content is too small, allow SIZING_GROW containers to expand
} else if (sizeToDistribute > 0 && growContainerCount > 0) {
float targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset));
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
float *minSize = xAxis ? &childElement->minDimensions.width : &childElement->minDimensions.height;
if (targetSize < *minSize) {
growContainerContentSize -= *minSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childOffset);
growContainerCount--;
targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
childOffset = -1;
continue;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type;
if (childSizing != CLAY__SIZING_TYPE_GROW) {
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
}
while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) {
float smallest = CLAY__MAXFLOAT;
float secondSmallest = CLAY__MAXFLOAT;
float widthToAdd = sizeToDistribute;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
if (Clay__FloatEqual(childSize, smallest)) { continue; }
if (childSize < smallest) {
secondSmallest = smallest;
smallest = childSize;
}
if (childSize > smallest) {
secondSmallest = CLAY__MIN(secondSmallest, childSize);
widthToAdd = secondSmallest - smallest;
}
}
widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max;
float previousWidth = *childSize;
if (Clay__FloatEqual(*childSize, smallest)) {
*childSize += widthToAdd;
if (*childSize >= maxSize) {
*childSize = maxSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
sizeToDistribute -= (*childSize - previousWidth);
}
*childSize = targetSize;
}
}
}
@ -2167,25 +2348,25 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset));
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
if (!xAxis && Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) {
continue; // Currently we don't support resizing aspect ratio images on the Y axis because it would break the ratio
}
// If we're laying out the children of a scroll panel, grow containers expand to the height of the inner content, not the outer container
float maxSize = parentSize - parentPadding;
// If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container
if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL)) {
Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig;
if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) {
maxSize = CLAY__MAX(maxSize, innerContentSize);
}
}
if (childSizing.type == CLAY__SIZING_TYPE_FIT) {
*childSize = CLAY__MAX(childSizing.size.minMax.min, CLAY__MIN(*childSize, maxSize));
} else if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
*childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max);
}
*childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize));
}
}
}
@ -2638,7 +2819,7 @@ void Clay__CalculateFinalLayout(void) {
.letterSpacing = textElementConfig->letterSpacing,
.lineHeight = textElementConfig->lineHeight,
}},
.userData = sharedConfig->userData,
.userData = textElementConfig->userData,
.id = Clay__HashNumber(lineIndex, currentElement->id).id,
.zIndex = root->zIndex,
.commandType = CLAY_RENDER_COMMAND_TYPE_TEXT,
@ -3567,17 +3748,17 @@ uint32_t Clay_MinMemorySize(void) {
Clay_Context* currentContext = Clay_GetCurrentContext();
if (currentContext) {
fakeContext.maxElementCount = currentContext->maxElementCount;
fakeContext.maxMeasureTextCacheWordCount = currentContext->maxElementCount;
fakeContext.maxMeasureTextCacheWordCount = currentContext->maxMeasureTextCacheWordCount;
}
// Reserve space in the arena for the context, important for calculating min memory size correctly
Clay__Context_Allocate_Arena(&fakeContext.internalArena);
Clay__InitializePersistentMemory(&fakeContext);
Clay__InitializeEphemeralMemory(&fakeContext);
return fakeContext.internalArena.nextAllocation + 128;
return (uint32_t)fakeContext.internalArena.nextAllocation + 128;
}
CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory")
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *memory) {
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory) {
Clay_Arena arena = {
.capacity = capacity,
.memory = (char *)memory
@ -3626,11 +3807,13 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) {
context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1));
Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great
Clay_BoundingBox elementBox = mapItem->boundingBox;
elementBox.x -= root->pointerOffset.x;
elementBox.y -= root->pointerOffset.y;
int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray));
Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId);
if (mapItem) {
if ((Clay__PointIsInsideRect(position, elementBox))) {
Clay_BoundingBox elementBox = mapItem->boundingBox;
elementBox.x -= root->pointerOffset.x;
elementBox.y -= root->pointerOffset.y;
if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)))) {
if (mapItem->onHoverFunction) {
mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData);
}
@ -3935,11 +4118,15 @@ Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) {
for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) {
Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i);
if (scrollContainerData->elementId == id.id) {
Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig;
if (!scrollElementConfig) { // This can happen on the first frame before a scroll container is declared
return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT;
}
return CLAY__INIT(Clay_ScrollContainerData) {
.scrollPosition = &scrollContainerData->scrollPosition,
.scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height },
.contentDimensions = scrollContainerData->contentSize,
.config = *Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig,
.config = *scrollElementConfig,
.found = true
};
}

View File

@ -17,6 +17,41 @@ void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
struct ResizeRenderData_ {
SDL_Window* window;
int windowWidth;
int windowHeight;
ClayVideoDemo_Data demoData;
SDL_Renderer* renderer;
SDL2_Font* fonts;
};
typedef struct ResizeRenderData_ ResizeRenderData;
int resizeRendering(void* userData, SDL_Event* event) {
ResizeRenderData *actualData = userData;
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED) {
SDL_Window* window = actualData->window;
int windowWidth = actualData->windowWidth;
int windowHeight = actualData->windowHeight;
ClayVideoDemo_Data demoData = actualData->demoData;
SDL_Renderer* renderer = actualData->renderer;
SDL2_Font* fonts = actualData->fonts;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight });
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands, fonts);
SDL_RenderPresent(renderer);
}
return 0;
}
int main(int argc, char *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError());
@ -48,9 +83,15 @@ int main(int argc, char *argv[]) {
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) {
fprintf(stderr, "Error: could not create window and renderer: %s", SDL_GetError());
}
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); //for antialiasing
window = SDL_CreateWindow("SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //for antialiasing
bool enableVsync = false;
if(enableVsync){ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);} //"SDL_RENDERER_ACCELERATED" is for antialiasing
else{renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);}
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); //for alpha blending
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
@ -67,6 +108,18 @@ int main(int argc, char *argv[]) {
double deltaTime = 0;
ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize();
ResizeRenderData userData = {
window, // SDL_Window*
windowWidth, // int
windowHeight, // int
demoData, // CustomShit
renderer, // SDL_Renderer*
fonts // SDL2_Font[1]
};
// add an event watcher that will render the screen while youre dragging the window to different sizes
SDL_AddEventWatch(resizeRendering, &userData);
while (true) {
Clay_Vector2 scrollDelta = {};
SDL_Event event;

View File

@ -14,7 +14,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
SDL
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG preview-3.1.6
GIT_TAG release-3.2.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
@ -34,11 +34,24 @@ message(STATUS "Using SDL_ttf via FetchContent")
FetchContent_MakeAvailable(SDL_ttf)
set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Download SDL_image
FetchContent_Declare(
SDL_image
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
GIT_TAG release-3.2.0 # Slightly risky to use main branch, but it's the only one available
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL_image via FetchContent")
FetchContent_MakeAvailable(SDL_image)
set_property(DIRECTORY "${SDL_image_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
SDL3_image::SDL3_image
)
add_custom_command(

View File

@ -19,13 +19,16 @@ static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
typedef struct app_state {
SDL_Window *window;
SDL_Renderer *renderer;
Clay_SDL3RendererData rendererData;
ClayVideoDemo_Data demoData;
} AppState;
SDL_Surface *sample_image;
static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
{
TTF_Font *font = gFonts[config->fontId];
TTF_Font **fonts = userData;
TTF_Font *font = fonts[config->fontId];
int width, height;
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
@ -54,19 +57,33 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
}
*appstate = state;
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->renderer)) {
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->rendererData.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);
state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer);
if (!state->rendererData.textEngine) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *));
if (!state->rendererData.fonts) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
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;
state->rendererData.fonts[FONT_ID] = font;
sample_image = IMG_Load("resources/sample.png");
/* Initialize Clay */
uint64_t totalMemorySize = Clay_MinMemorySize();
@ -78,7 +95,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
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);
Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts);
state->demoData = ClayVideoDemo_Initialize();
@ -99,10 +116,14 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
break;
case SDL_EVENT_MOUSE_MOTION:
Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
event->motion.state & SDL_BUTTON_LEFT);
event->motion.state & SDL_BUTTON_LMASK);
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y },
event->button.button == SDL_BUTTON_LEFT);
break;
case SDL_EVENT_MOUSE_WHEEL:
Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->motion.xrel, event->motion.yrel }, 0.01f);
Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f);
break;
default:
break;
@ -117,12 +138,12 @@ SDL_AppResult SDL_AppIterate(void *appstate)
Clay_RenderCommandArray render_commands = ClayVideoDemo_CreateLayout(&state->demoData);
SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, 255);
SDL_RenderClear(state->renderer);
SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255);
SDL_RenderClear(state->rendererData.renderer);
SDL_RenderClayCommands(state->renderer, &render_commands);
SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands);
SDL_RenderPresent(state->renderer);
SDL_RenderPresent(state->rendererData.renderer);
return SDL_APP_CONTINUE;
}
@ -138,12 +159,23 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
AppState *state = appstate;
if (state) {
if (state->renderer)
SDL_DestroyRenderer(state->renderer);
if (state->rendererData.renderer)
SDL_DestroyRenderer(state->rendererData.renderer);
if (state->window)
SDL_DestroyWindow(state->window);
if (state->rendererData.fonts) {
for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) {
TTF_CloseFont(state->rendererData.fonts[i]);
}
SDL_free(state->rendererData.fonts);
}
if (state->rendererData.textEngine)
TTF_DestroyRendererTextEngine(state->rendererData.textEngine);
SDL_free(state);
}
TTF_Quit();

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View File

@ -119,6 +119,7 @@
{name: 'bottomRight', type: 'float'},
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },

View File

@ -119,6 +119,7 @@
{name: 'bottomRight', type: 'float'},
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },

View File

@ -50,4 +50,6 @@ int main(void) {
Clay_Raylib_Render(renderCommands, fonts);
EndDrawing();
}
// This function is new since the video was published
Clay_Raylib_Close();
}

View File

@ -72,4 +72,6 @@ int main(void) {
Clay_Raylib_Render(renderCommandsBottom, fonts);
EndDrawing();
}
Clay_Raylib_Close();
}

View File

@ -80,7 +80,7 @@ Clay_RenderCommandArray CreateLayout(void) {
CLAY_TEXT(CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."),
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0,0,0,255} }));
CLAY({ .id = CLAY_ID("Photos2"), .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, 255} }) {
CLAY({ .id = CLAY_ID("Photos2"), .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, Clay_Hovered() ? 120 : 255} }) {
CLAY({ .id = CLAY_ID("Picture4"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
CLAY({ .id = CLAY_ID("Picture5"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
CLAY({ .id = CLAY_ID("Picture6"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
@ -226,8 +226,8 @@ int main(void) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 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"));
Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT);
profilePicture = LoadTexture("resources/profile-picture.png");
Font fonts[2];
fonts[FONT_ID_BODY_24] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
@ -250,5 +250,6 @@ int main(void) {
}
UpdateDrawFrame(fonts);
}
Clay_Raylib_Close();
return 0;
}

View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.27)
project(sokol_corner_radius C)
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
add_executable(sokol_corner_radius WIN32 main.c)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_corner_radius)
else()
add_executable(sokol_corner_radius main.c)
endif()
target_link_libraries(sokol_corner_radius PUBLIC sokol)

View File

@ -0,0 +1,110 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
Clay_SetMeasureTextFunction(sclay_measure_text, NULL);
}
Clay_RenderCommandArray CornerRadiusTest(){
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
CLAY({ .id = CLAY_ID("OuterContainer"),
.backgroundColor = {43, 41, 51, 255},
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = {0, 0, 20, 20},
.childGap = 20
}
}) {
for(int i = 0; i < 6; ++i){
CLAY({ .id = CLAY_IDI("Row", i),
.layout = {
.layoutDirection = CLAY_LEFT_TO_RIGHT,
.sizing = layoutExpand,
.padding = {20, 20, 0, 0},
.childGap = 20
}
}) {
for(int j = 0; j < 6; ++j){
CLAY({ .id = CLAY_IDI("Tile", i*6+j),
.backgroundColor = {120, 140, 255, 128},
.cornerRadius = {(i%3)*15, (j%3)*15, (i/2)*15, (j/2)*15},
.border = {
.color = {120, 140, 255, 255},
.width = {3, 9, 6, 12, 0},
},
.layout = { .sizing = layoutExpand }
});
}
}
}
}
return Clay_EndLayout();
}
static void frame() {
sclay_new_frame();
Clay_RenderCommandArray renderCommands = CornerRadiusTest();
sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
sgl_matrix_mode_modelview();
sgl_load_identity();
sclay_render(renderCommands, NULL);
sgl_draw();
sg_end_pass();
sg_commit();
}
static void event(const sapp_event *ev) {
if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){
Clay_SetDebugModeEnabled(true);
} else {
sclay_handle_event(ev);
}
}
static void cleanup() {
sclay_shutdown();
sgl_shutdown();
sg_shutdown();
}
sapp_desc sokol_main(int argc, char **argv) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.window_title = "Clay - Corner Radius Test",
.width = 800,
.height = 600,
.icon.sokol_default = true,
.logger.func = slog_func,
};
}

View File

@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.27)
project(sokol_video_demo C)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# Linux -pthread shenanigans
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
endif()
FetchContent_Declare(
fontstash
GIT_REPOSITORY "https://github.com/memononen/fontstash.git"
GIT_TAG "b5ddc9741061343740d85d636d782ed3e07cf7be"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fontstash)
FetchContent_Declare(
sokol
GIT_REPOSITORY "https://github.com/floooh/sokol.git"
GIT_TAG "da9de496f938b7575eff7f01ab774d77469bd390"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(sokol)
set(sokol_HEADERS
${sokol_SOURCE_DIR}/sokol_app.h
${sokol_SOURCE_DIR}/sokol_gfx.h
${sokol_SOURCE_DIR}/sokol_glue.h
${sokol_SOURCE_DIR}/sokol_log.h
${sokol_SOURCE_DIR}/util/sokol_gl.h
${fontstash_SOURCE_DIR}/src/fontstash.h
${sokol_SOURCE_DIR}/util/sokol_fontstash.h)
if(CMAKE_SYSTEM_NAME STREQUAL Darwin)
add_library(sokol STATIC sokol.c ${sokol_HEADERS})
target_compile_options(sokol PRIVATE -x objective-c)
target_link_libraries(sokol PUBLIC
"-framework QuartzCore"
"-framework Cocoa"
"-framework MetalKit"
"-framework Metal")
else()
add_library(sokol STATIC sokol.c ${sokol_HEADERS})
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_compile_definitions(sokol PRIVATE SOKOL_GLCORE=1)
target_link_libraries(sokol INTERFACE X11 Xi Xcursor GL dl m)
target_link_libraries(sokol PUBLIC Threads::Threads)
endif()
endif()
target_include_directories(sokol INTERFACE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src
PRIVATE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src)
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
add_executable(sokol_video_demo WIN32 main.c)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_video_demo)
else()
add_executable(sokol_video_demo main.c)
endif()
target_link_libraries(sokol_video_demo PUBLIC sokol)
add_custom_command(
TARGET sokol_video_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -0,0 +1,77 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
#include "../shared-layouts/clay-video-demo.c"
static ClayVideoDemo_Data demoData;
static sclay_font_t fonts[1];
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
fonts[FONT_ID_BODY_16] = sclay_add_font("resources/Roboto-Regular.ttf");
Clay_SetMeasureTextFunction(sclay_measure_text, &fonts);
demoData = ClayVideoDemo_Initialize();
}
static void frame() {
sclay_new_frame();
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
sgl_matrix_mode_modelview();
sgl_load_identity();
sclay_render(renderCommands, fonts);
sgl_draw();
sg_end_pass();
sg_commit();
}
static void event(const sapp_event *ev) {
if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){
Clay_SetDebugModeEnabled(true);
} else {
sclay_handle_event(ev);
}
}
static void cleanup() {
sclay_shutdown();
sgl_shutdown();
sg_shutdown();
}
sapp_desc sokol_main(int argc, char **argv) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.window_title = "Clay - Sokol Renderer Example",
.width = 800,
.height = 600,
.high_dpi = true,
.icon.sokol_default = true,
.logger.func = slog_func,
};
}

Binary file not shown.

View File

@ -0,0 +1,24 @@
#define SOKOL_IMPL
#if defined(_WIN32)
#define SOKOL_D3D11
#elif defined(__EMSCRIPTEN__)
#define SOKOL_GLES2
#elif defined(__APPLE__)
#define SOKOL_METAL
#else
#define SOKOL_GLCORE33
#endif
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_time.h"
#include "sokol_fetch.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#include "util/sokol_gl.h"
#include <stdio.h> // fontstash requires this
#include <stdlib.h> // fontstash requires this
#define FONTSTASH_IMPLEMENTATION
#include "fontstash.h"
#define SOKOL_FONTSTASH_IMPL
#include "util/sokol_fontstash.h"

View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.27)
project(win32_gdi C)
set(CMAKE_C_STANDARD 99)
add_executable(win32_gdi WIN32 main.c)
target_compile_options(win32_gdi PUBLIC)
target_include_directories(win32_gdi PUBLIC .)
add_custom_command(
TARGET win32_gdi POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -0,0 +1,4 @@
# to build this, install mingw
gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output

235
examples/win32_gdi/main.c Normal file
View File

@ -0,0 +1,235 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "../../renderers/win32_gdi/clay_renderer_gdi.c"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../shared-layouts/clay-video-demo.c"
ClayVideoDemo_Data demo_data;
#define APPNAME "Clay GDI Example"
char szAppName[] = APPNAME; // The name of this application
char szTitle[] = APPNAME; // The title bar text
void CenterWindow(HWND hWnd);
long lastMsgTime = 0;
bool ui_debug_mode;
HFONT fonts[1];
#ifndef RECTWIDTH
#define RECTWIDTH(rc) ((rc).right - (rc).left)
#endif
#ifndef RECTHEIGHT
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)
#endif
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
// ----------------------- first and last
case WM_CREATE:
CenterWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_MOUSEWHEEL: // scrolling data
{
short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed
long now = GetMessageTime();
float dt = (now - lastMsgTime) / 1000.00;
lastMsgTime = now;
// little hacky hack to make scrolling *feel* right
if (abs(zDelta) > 100)
{
zDelta = zDelta * .012;
}
Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_RBUTTONUP:
case WM_LBUTTONUP:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MOUSEMOVE: // mouse events
{
short mouseX = GET_X_LPARAM(lParam);
short mouseY = GET_Y_LPARAM(lParam);
short mouseButtons = LOWORD(wParam);
Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_SIZE: // resize events
{
RECT r = {0};
if (GetClientRect(hwnd, &r))
{
Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left};
Clay_SetLayoutDimensions(dim);
}
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_KEYDOWN:
if (VK_ESCAPE == wParam)
{
DestroyWindow(hwnd);
break;
}
if (wParam == VK_F12)
{
Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode);
break;
}
printf("Key Pressed: %d\r\n", wParam);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
// ----------------------- render
case WM_PAINT:
{
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
Clay_Win32_Render(hwnd, renderCommands, fonts);
break;
}
// ----------------------- let windows do all other stuff
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
bool didAllocConsole = false;
void HandleClayErrors(Clay_ErrorData errorData)
{
if (!didAllocConsole)
{
didAllocConsole = AllocConsole();
}
printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars);
}
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASS wc;
HWND hwnd;
demo_data = ClayVideoDemo_Initialize();
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published
Clay_Win32_SetRendererFlags(CLAYGDI_RF_ALPHABLEND | CLAYGDI_RF_SMOOTHCORNERS);
// Initialize clay fonts and text drawing
fonts[FONT_ID_BODY_16] = Clay_Win32_SimpleCreateFont("resources/Roboto-Regular.ttf", "Roboto", -11, FW_NORMAL);
Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, fonts);
ZeroMemory(&wc, sizeof wc);
wc.hInstance = hInstance;
wc.lpszClassName = szAppName;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (FALSE == RegisterClass(&wc))
return 0;
// Calculate window rectangle by given client size
// TODO: AdjustWindowRectExForDpi for DPI support
RECT rcWindow = { .right = 800, .bottom = 600 };
AdjustWindowRect(&rcWindow, WS_OVERLAPPEDWINDOW, FALSE);
hwnd = CreateWindow(
szAppName,
szTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
RECTWIDTH(rcWindow), // CW_USEDEFAULT,
RECTHEIGHT(rcWindow), // CW_USEDEFAULT,
0,
0,
hInstance,
0);
if (hwnd == NULL)
return 0;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void CenterWindow(HWND hwnd_self)
{
HWND hwnd_parent;
RECT rw_self, rc_parent, rw_parent;
int xpos, ypos;
hwnd_parent = GetParent(hwnd_self);
if (NULL == hwnd_parent)
hwnd_parent = GetDesktopWindow();
GetWindowRect(hwnd_parent, &rw_parent);
GetClientRect(hwnd_parent, &rc_parent);
GetWindowRect(hwnd_self, &rw_self);
xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2;
ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2;
SetWindowPos(
hwnd_self, NULL,
xpos, ypos, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
//+---------------------------------------------------------------------------

Binary file not shown.

View File

@ -3,6 +3,11 @@
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <stdio.h>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159
#endif
#define CLAY_COLOR_TO_SDL_COLOR_ARGS(color) color.r, color.g, color.b, color.a
@ -48,8 +53,8 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect re
int indexCount = 0, vertexCount = 0;
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
const float clampedRadius = SDL_min(cornerRadius, minRadius);
const float maxRadius = SDL_min(rect.w, rect.h) / 2.0f;
const float clampedRadius = SDL_min(cornerRadius, maxRadius);
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)clampedRadius * 0.5f);
@ -141,6 +146,121 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect re
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
}
//all rendering is performed by a single SDL call, using twi sets of arcing triangles, inner and outer, that fit together; along with two tringles to fill the end gaps.
static void SDL_RenderCornerBorder(SDL_Renderer *renderer, Clay_BoundingBox* boundingBox, Clay_BorderRenderData* config, int cornerIndex, Clay_Color _color){
/////////////////////////////////
//The arc is constructed of outer triangles and inner triangles (if needed).
//First three vertices are first outer triangle's vertices
//Each two vertices after that are the inner-middle and second-outer vertex of
//each outer triangle after the first, because there first-outer vertex is equal to the
//second-outer vertex of the previous triangle. Indices set accordingly.
//The final two vertices are the missing vertices for the first and last inner triangles (if needed)
//Everything is in clockwise order (CW).
/////////////////////////////////
const SDL_Color color = (SDL_Color) {
.r = (Uint8)_color.r,
.g = (Uint8)_color.g,
.b = (Uint8)_color.b,
.a = (Uint8)_color.a,
};
float centerX, centerY, outerRadius, clampedRadius, startAngle, borderWidth;
const float maxRadius = SDL_min(boundingBox->width, boundingBox->height) / 2.0f;
SDL_Vertex vertices[512];
int indices[512];
int indexCount = 0, vertexCount = 0;
switch (cornerIndex) {
case(0):
startAngle = M_PI;
outerRadius = SDL_min(config->cornerRadius.topLeft, maxRadius);
centerX = boundingBox->x + outerRadius;
centerY = boundingBox->y + outerRadius;
borderWidth = config->width.top;
break;
case(1):
startAngle = 3*M_PI/2;
outerRadius = SDL_min(config->cornerRadius.topRight, maxRadius);
centerX = boundingBox->x + boundingBox->width - outerRadius;
centerY = boundingBox->y + outerRadius;
borderWidth = config->width.top;
break;
case(2):
startAngle = 0;
outerRadius = SDL_min(config->cornerRadius.bottomRight, maxRadius);
centerX = boundingBox->x + boundingBox->width - outerRadius;
centerY = boundingBox->y + boundingBox->height - outerRadius;
borderWidth = config->width.bottom;
break;
case(3):
startAngle = M_PI/2;
outerRadius = SDL_min(config->cornerRadius.bottomLeft, maxRadius);
centerX = boundingBox->x + outerRadius;
centerY = boundingBox->y + boundingBox->height - outerRadius;
borderWidth = config->width.bottom;
break;
default: break;
}
const float innerRadius = outerRadius - borderWidth;
const int minNumOuterTriangles = NUM_CIRCLE_SEGMENTS;
const int numOuterTriangles = SDL_max(minNumOuterTriangles, ceilf(outerRadius * 0.5f));
const float angleStep = M_PI / (2.0*(float)numOuterTriangles);
//outer triangles, in CW order
for (int i = 0; i < numOuterTriangles; i++) {
float angle1 = startAngle + i*angleStep; //first-outer vertex angle
float angle2 = startAngle + ((float)i + 0.5) * angleStep; //inner-middle vertex angle
float angle3 = startAngle + (i+1)*angleStep; // second-outer vertex angle
if( i == 0){ //first outer triangle
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle1) * outerRadius, centerY + SDL_sinf(angle1) * outerRadius}, color, {0, 0} }; //vertex index = 0
}
indices[indexCount++] = vertexCount - 1; //will be second-outer vertex of last outer triangle if not first outer triangle.
vertices[vertexCount++] = (innerRadius > 0)?
(SDL_Vertex){ {centerX + SDL_cosf(angle2) * (innerRadius), centerY + SDL_sinf(angle2) * (innerRadius)}, color, {0, 0}}:
(SDL_Vertex){ {centerX, centerY }, color, {0, 0}};
indices[indexCount++] = vertexCount - 1;
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle3) * outerRadius, centerY + SDL_sinf(angle3) * outerRadius}, color, {0, 0} };
indices[indexCount++] = vertexCount - 1;
}
if(innerRadius > 0){
// inner triangles in CW order (except the first and last)
for (int i = 0; i < numOuterTriangles - 1; i++){ //skip the last outer triangle
if(i==0){ //first outer triangle -> second inner triangle
indices[indexCount++] = 1; //inner-middle vertex of first outer triangle
indices[indexCount++] = 2; //second-outer vertex of first outer triangle
indices[indexCount++] = 3; //innder-middle vertex of second-outer triangle
}else{
int baseIndex = 3; //skip first outer triangle
indices[indexCount++] = baseIndex + (i-1)*2; // inner-middle vertex of current outer triangle
indices[indexCount++] = baseIndex + (i-1)*2 + 1; // second-outer vertex of current outer triangle
indices[indexCount++] = baseIndex + (i-1)*2 + 2; // inner-middle vertex of next outer triangle
}
}
float endAngle = startAngle + M_PI/2.0;
//last inner triangle
indices[indexCount++] = vertexCount - 2; //inner-middle vertex of last outer triangle
indices[indexCount++] = vertexCount - 1; //second-outer vertex of last outer triangle
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(endAngle) * innerRadius, centerY + SDL_sinf(endAngle) * innerRadius}, color, {0, 0} }; //missing vertex
indices[indexCount++] = vertexCount - 1;
// //first inner triangle
indices[indexCount++] = 0; //first-outer vertex of first outer triangle
indices[indexCount++] = 1; //inner-middle vertex of first outer triangle
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(startAngle) * innerRadius, centerY + SDL_sinf(startAngle) * innerRadius}, color, {0, 0} }; //missing vertex
indices[indexCount++] = vertexCount - 1;
}
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
}
SDL_Rect currentClippingRectangle;
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands, SDL2_Font *fonts)
@ -228,35 +348,74 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData *config = &renderCommand->renderData.border;
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
if (config->width.left > 0) {
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
SDL_FRect rect = { boundingBox.x, boundingBox.y + config->cornerRadius.topLeft, config->width.left, boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft };
SDL_RenderFillRectF(renderer, &rect);
}
if(boundingBox.width > 0 & boundingBox.height > 0){
const float maxRadius = SDL_min(boundingBox.width, boundingBox.height) / 2.0f;
if (config->width.right > 0) {
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + config->cornerRadius.topRight, config->width.right, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.left > 0) {
const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topLeft, maxRadius);
const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius);
SDL_FRect rect = {
boundingBox.x,
boundingBox.y + clampedRadiusTop,
(float)config->width.left,
(float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom
};
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.right > 0) {
const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topRight, maxRadius);
const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomRight, maxRadius);
SDL_FRect rect = {
boundingBox.x + boundingBox.width - config->width.right,
boundingBox.y + clampedRadiusTop,
(float)config->width.right,
(float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom
};
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.top > 0) {
const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.topLeft, maxRadius);
const float clampedRadiusRight = SDL_min((float)config->cornerRadius.topRight, maxRadius);
SDL_FRect rect = {
boundingBox.x + clampedRadiusLeft,
boundingBox.y,
boundingBox.width - clampedRadiusLeft - clampedRadiusRight,
(float)config->width.top };
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.bottom > 0) {
const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius);
const float clampedRadiusRight = SDL_min((float)config->cornerRadius.bottomRight, maxRadius);
SDL_FRect rect = {
boundingBox.x + clampedRadiusLeft,
boundingBox.y + boundingBox.height - config->width.bottom,
boundingBox.width - clampedRadiusLeft - clampedRadiusRight,
(float)config->width.bottom
};
SDL_RenderFillRectF(renderer, &rect);
}
//corner index: 0->3 topLeft -> CW -> bottonLeft
if (config->width.top > 0 & config->cornerRadius.topLeft > 0) {
SDL_RenderCornerBorder(renderer, &boundingBox, config, 0, config->color);
}
if (config->width.right > 0) {
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + config->cornerRadius.topRight, config->width.right, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.top > 0 & config->cornerRadius.topRight> 0) {
SDL_RenderCornerBorder(renderer, &boundingBox, config, 1, config->color);
}
if (config->width.top > 0) {
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
SDL_FRect rect = { boundingBox.x + config->cornerRadius.topLeft, boundingBox.y, boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight, config->width.top };
SDL_RenderFillRectF(renderer, &rect);
}
if (config->width.bottom > 0 & config->cornerRadius.bottomRight > 0) {
SDL_RenderCornerBorder(renderer, &boundingBox, config, 2, config->color);
}
if (config->width.bottom > 0) {
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
SDL_FRect rect = { boundingBox.x + config->cornerRadius.bottomLeft, boundingBox.y + boundingBox.height - config->width.bottom, boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight, config->width.bottom };
SDL_RenderFillRectF(renderer, &rect);
if (config->width.bottom > 0 & config->cornerRadius.bottomLeft > 0) {
SDL_RenderCornerBorder(renderer, &boundingBox, config, 3, config->color);
}
}
break;

View File

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

View File

@ -2,16 +2,20 @@
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <SDL3_image/SDL_image.h>
typedef struct {
SDL_Renderer *renderer;
TTF_TextEngine *textEngine;
TTF_Font **fonts;
} Clay_SDL3RendererData;
/* 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) {
static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, 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;
@ -109,11 +113,11 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer *renderer, const SDL_FRect re
indices[indexCount++] = vertexCount - 1; //LT
// Render everything
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
SDL_RenderGeometry(rendererData->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);
static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
SDL_SetRenderDrawColor(rendererData->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);
@ -133,38 +137,37 @@ static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const
SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius),
SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) };
}
SDL_RenderLines(renderer, points, numCircleSegments + 1);
SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1);
}
}
static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArray *rcommands)
SDL_Rect currentClippingRectangle;
static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, 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 };
const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height };
switch (rcmd->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData *config = &rcmd->renderData.rectangle;
SDL_SetRenderDrawColor(renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
SDL_SetRenderDrawBlendMode(rendererData->renderer, SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
if (config->cornerRadius.topLeft > 0) {
SDL_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, config->backgroundColor);
SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor);
} else {
SDL_RenderFillRect(renderer, &rect);
SDL_RenderFillRect(rendererData->renderer, &rect);
}
} break;
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData *config = &rcmd->renderData.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, config->stringContents.chars, config->stringContents.length, color);
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_RenderTexture(renderer, texture, NULL, &rect);
SDL_DestroySurface(surface);
SDL_DestroyTexture(texture);
TTF_Font *font = rendererData->fonts[config->fontId];
TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length);
TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a);
TTF_DrawRendererText(text, rect.x, rect.y);
TTF_DestroyText(text);
} break;
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData *config = &rcmd->renderData.border;
@ -177,61 +180,85 @@ static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArr
.bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius)
};
//edges
SDL_SetRenderDrawColor(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
if (config->width.left > 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->width.left, length };
SDL_RenderFillRect(renderer, &line);
SDL_RenderFillRect(rendererData->renderer, &line);
}
if (config->width.right > 0) {
const float starting_x = rect.x + rect.w - (float)config->width.right;
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->width.right, length };
SDL_RenderFillRect(renderer, &line);
SDL_RenderFillRect(rendererData->renderer, &line);
}
if (config->width.top > 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->width.top };
SDL_RenderFillRect(renderer, &line);
SDL_RenderFillRect(rendererData->renderer, &line);
}
if (config->width.bottom > 0) {
const float starting_x = rect.x + clampedRadii.bottomLeft;
const float starting_y = rect.y + rect.h - (float)config->width.bottom;
const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
SDL_FRect line = { starting_x, starting_y, length, config->width.bottom };
SDL_SetRenderDrawColor(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
SDL_RenderFillRect(renderer, &line);
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
SDL_RenderFillRect(rendererData->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,
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
180.0f, 270.0f, config->width.top, config->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,
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
270.0f, 360.0f, config->width.top, config->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,
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
90.0f, 180.0f, config->width.bottom, config->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,
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
0.0f, 90.0f, config->width.bottom, config->color);
}
} break;
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
Clay_BoundingBox boundingBox = rcmd->boundingBox;
currentClippingRectangle = (SDL_Rect) {
.x = boundingBox.x,
.y = boundingBox.y,
.w = boundingBox.width,
.h = boundingBox.height,
};
SDL_SetRenderClipRect(rendererData->renderer, &currentClippingRectangle);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
SDL_SetRenderClipRect(rendererData->renderer, NULL);
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
SDL_Surface *image = (SDL_Surface *)rcmd->renderData.image.imageData;
SDL_Texture *texture = SDL_CreateTextureFromSurface(rendererData->renderer, image);
const SDL_FRect dest = { rect.x, rect.y, rect.w, rect.h };
SDL_RenderTexture(rendererData->renderer, texture, NULL, &dest);
SDL_DestroyTexture(texture);
break;
}
default:
SDL_Log("Unknown render command type: %d", rcmd->commandType);
}

View File

@ -91,8 +91,11 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex
float textHeight = config->fontSize;
Font* fonts = (Font*)userData;
Font fontToUse = fonts[config->fontId];
// Font failed to load, likely the fonts are in the wrong place relative to the execution dir
if (!fontToUse.glyphs) return textSize;
// Font failed to load, likely the fonts are in the wrong place relative to the execution dir.
// RayLib ships with a default font, so we can continue with that built in one.
if (!fontToUse.glyphs) {
fontToUse = GetFontDefault();
}
float scaleFactor = config->fontSize/(float)fontToUse.baseSize;
@ -122,6 +125,21 @@ void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned i
// EnableEventWaiting();
}
// A MALLOC'd buffer, that we keep modifying inorder to save from so many Malloc and Free Calls.
// Call Clay_Raylib_Close() to free
static char *temp_render_buffer = NULL;
static int temp_render_buffer_len = 0;
// Call after closing the window to clean up the render buffer
void Clay_Raylib_Close()
{
if(temp_render_buffer) free(temp_render_buffer);
temp_render_buffer_len = 0;
CloseWindow();
}
void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
{
for (int j = 0; j < renderCommands.length; j++)
@ -131,14 +149,23 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
switch (renderCommand->commandType)
{
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_TextRenderData *textData = &renderCommand->renderData.text;
char *cloned = (char *)malloc(textData->stringContents.length + 1);
memcpy(cloned, textData->stringContents.chars, textData->stringContents.length);
cloned[textData->stringContents.length] = '\0';
Font fontToUse = fonts[textData->fontId];
DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
free(cloned);
int strlen = textData->stringContents.length + 1;
if(strlen > temp_render_buffer_len) {
// Grow the temp buffer if we need a larger string
if(temp_render_buffer) free(temp_render_buffer);
temp_render_buffer = (char *) malloc(strlen);
temp_render_buffer_len = strlen;
}
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
memcpy(temp_render_buffer, textData->stringContents.chars, textData->stringContents.length);
temp_render_buffer[textData->stringContents.length] = '\0';
DrawTextEx(fontToUse, temp_render_buffer, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
@ -198,7 +225,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->width.top), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
}
if (config->cornerRadius.bottomLeft > 0) {
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.top), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.bottom), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
}
if (config->cornerRadius.bottomRight > 0) {
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));

View File

@ -0,0 +1,450 @@
#ifndef SOKOL_CLAY_INCLUDED
#define SOKOL_CLAY_INCLUDED (1)
/*
sokol_clay.h -- drop-in Clay renderer for sokol_gfx.h
Do this:
#define SOKOL_CLAY_IMPL
before you include this file in *one* C file to create the
implementation.
Optionally provide the following configuration define both before including the
the declaration and implementation:
SOKOL_CLAY_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details)
Include the following headers before sokol_clay.h (both before including
the declaration and implementation):
sokol_gl.h
sokol_fontstash.h
sokol_app.h (except SOKOL_CLAY_NO_SOKOL_APP)
clay.h
FEATURE OVERVIEW:
=================
sokol_clay.h implements the rendering and event-handling code for Clay
(https://github.com/nicbarker/clay) on top of sokol_gl.h and (optionally)
sokol_app.h.
Since sokol_fontstash.h already depends on sokol_gl.h, the rendering is
implemented using sokol_gl calls. (TODO: make fontstash optional?)
The sokol_app.h dependency is optional and used for input event handling.
If you only use sokol_gfx.h but not sokol_app.h in your application,
define SOKOL_CLAY_NO_SOKOL_APP before including the implementation
of sokol_clay.h, this will remove any dependency to sokol_app.h, but
you must call sclay_set_layout_dimensions and handle input yourself.
sokol_clay.h is not thread-safe, all calls must be made from the
same thread where sokol_gfx.h is running.
HOWTO:
======
--- To initialize sokol-clay, call sclay_setup(). This can be done
before or after Clay_Initialize.
--- Create an array of sclay_font_t and fill it by calling one of:
sclay_font_t sclay_add_font(const char *filename);
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen);
The fontId value in Clay corresponds to indices in this array. After calling
Clay_Initialize but before calling any layout code, do this:
Clay_SetMeasureTextFunction(sclay_measure_text, &fonts);
where `fonts` is the abovementioned array.
--- At the start of a frame, call sclay_new_frame() if you're using sokol_app.h.
If you're not using sokol_app.h, call:
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale);
at the start of the frame (or just when the window is resized.)
Either way, do some layout, then at the end of the frame call sclay_render:
sg_begin_pass(...)
// other rendering...
sclay_render(renderCommands, &fonts);
// other rendering...
sgl_draw();
sg_end_pass();
sg_commit();
One caveat: sclay_render assumes the default gl view matrix, and handles scaling
automatically. If you've adjusted the view matrix, remember to first call:
sgl_matrix_mode_modelview();
sgl_load_identity();
before calling sclay_render.
--- if you're using sokol_app.h, from inside the sokol_app.h event callback,
call:
void sclay_handle_event(const sapp_event* ev);
Unfortunately Clay does not currently provide feedback on whether a mouse
click was handled or not.
--- finally, on application shutdown, call
sclay_shutdown()
*/
#if !defined(SOKOL_CLAY_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED)
#error "Please include sokol_app.h before sokol_clay.h (or define SOKOL_CLAY_NO_SOKOL_APP)"
#endif
typedef int sclay_font_t;
void sclay_setup();
void sclay_shutdown();
sclay_font_t sclay_add_font(const char *filename);
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen);
Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData);
#ifndef SOKOL_CLAY_NO_SOKOL_APP
void sclay_new_frame();
void sclay_handle_event(const sapp_event *ev);
#endif /* SOKOL_CLAY_NO_SOKOL_APP */
/* Use this if you don't call sclay_new_frame. `size` is the "virtual" size which
* your layout is relative to (ie. the actual framebuffer size divided by dpi_scale.)
* Set dpi_scale to 1 if you're not using high-dpi support. */
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale);
void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts);
#endif /* SOKOL_CLAY_INCLUDED */
#ifdef SOKOL_CLAY_IMPL
#define SOKOL_CLAY_IMPL_INCLUDED (1)
#ifndef SOKOL_GL_INCLUDED
#error "Please include sokol_gl.h before sokol_clay.h"
#endif
#ifndef SOKOL_FONTSTASH_INCLUDED
#error "Please include sokol_fontstash.h before sokol_clay.h"
#endif
#ifndef CLAY_HEADER
#error "Please include clay.h before sokol_clay.h"
#endif
typedef struct {
sgl_pipeline pip;
#ifndef SOKOL_CLAY_NO_SOKOL_APP
Clay_Vector2 mouse_pos, scroll;
bool mouse_down;
#endif
Clay_Dimensions size;
float dpi_scale;
FONScontext *fonts;
} _sclay_state_t;
static _sclay_state_t _sclay;
void sclay_setup() {
_sclay.pip = sgl_make_pipeline(&(sg_pipeline_desc){
.colors[0] = {
.blend = {
.enabled = true,
.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
},
}
});
#ifndef SOKOL_CLAY_NO_SOKOL_APP
_sclay.mouse_pos = (Clay_Vector2){0, 0};
_sclay.scroll = (Clay_Vector2){0, 0};
_sclay.mouse_down = false;
#endif
_sclay.size = (Clay_Dimensions){1, 1};
_sclay.dpi_scale = 1;
_sclay.fonts = sfons_create(&(sfons_desc_t){ 0 });
//TODO clay error handler?
}
void sclay_shutdown() {
sgl_destroy_pipeline(_sclay.pip);
sfons_destroy(_sclay.fonts);
}
#ifndef SOKOL_CLAY_NO_SOKOL_APP
void sclay_handle_event(const sapp_event* ev) {
switch(ev->type){
case SAPP_EVENTTYPE_MOUSE_MOVE:
_sclay.mouse_pos.x = ev->mouse_x / _sclay.dpi_scale;
_sclay.mouse_pos.y = ev->mouse_y / _sclay.dpi_scale;
break;
case SAPP_EVENTTYPE_MOUSE_DOWN:
_sclay.mouse_down = true;
break;
case SAPP_EVENTTYPE_MOUSE_UP:
_sclay.mouse_down = false;
break;
case SAPP_EVENTTYPE_MOUSE_SCROLL:
_sclay.scroll.x += ev->scroll_x;
_sclay.scroll.y += ev->scroll_y;
break;
default: break;
}
}
void sclay_new_frame() {
sclay_set_layout_dimensions((Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() },
sapp_dpi_scale());
Clay_SetPointerState(_sclay.mouse_pos, _sclay.mouse_down);
Clay_UpdateScrollContainers(true, _sclay.scroll, sapp_frame_duration());
_sclay.scroll = (Clay_Vector2){0, 0};
}
#endif /* SOKOL_CLAY_NO_SOKOL_APP */
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale) {
size.width /= dpi_scale;
size.height /= dpi_scale;
_sclay.size = size;
if(_sclay.dpi_scale != dpi_scale){
_sclay.dpi_scale = dpi_scale;
Clay_ResetMeasureTextCache();
}
Clay_SetLayoutDimensions(size);
}
sclay_font_t sclay_add_font(const char *filename) {
//TODO log something if we get FONS_INVALID
return fonsAddFont(_sclay.fonts, "", filename);
}
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen) {
//TODO log something if we get FONS_INVALID
return fonsAddFontMem(_sclay.fonts, "", data, dataLen, false);
}
Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
sclay_font_t *fonts = (sclay_font_t *)userData;
if(!fonts) return (Clay_Dimensions){ 0 };
fonsSetFont(_sclay.fonts, fonts[config->fontId]);
fonsSetSize(_sclay.fonts, config->fontSize);
fonsSetSpacing(_sclay.fonts, config->letterSpacing);
float ascent, descent, lineh;
fonsVertMetrics(_sclay.fonts, &ascent, &descent, &lineh);
return (Clay_Dimensions) {
.width = fonsTextBounds(_sclay.fonts, 0, 0, text.chars, text.chars + text.length, NULL),
.height = ascent - descent
};
}
static void _draw_rect(float x, float y, float w, float h){
sgl_v2f(x, y);
sgl_v2f(x, y);
sgl_v2f(x+w, y);
sgl_v2f(x, y+h);
sgl_v2f(x+w, y+h);
sgl_v2f(x+w, y+h);
}
static float _SIN[16] = {
0.000000f, 0.104528f, 0.207912f, 0.309017f,
0.406737f, 0.500000f, 0.587785f, 0.669131f,
0.743145f, 0.809017f, 0.866025f, 0.913545f,
0.951057f, 0.978148f, 0.994522f, 1.000000f,
};
/* rx,ry = radius */
static void _draw_corner(float x, float y, float rx, float ry){
x -= rx;
y -= ry;
sgl_v2f(x, y);
for(int i = 0; i < 16; ++i){
sgl_v2f(x, y);
sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i]));
}
sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]));
}
/* rx,ry = radius ix,iy = inner radius */
static void _draw_corner_border(float x, float y, float rx, float ry, float ix, float iy){
x -= rx;
y -= ry;
sgl_v2f(x+(ix*_SIN[15]), y+(iy*_SIN[0]));
for(int i = 0; i < 16; ++i){
sgl_v2f(x+(ix*_SIN[15-i]), y+(iy*_SIN[i]));
sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i]));
}
sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]));
}
void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts) {
sgl_matrix_mode_modelview();
sgl_translate(-1.0f, 1.0f, 0.0f);
sgl_scale(2.0f/_sclay.size.width, -2.0f/_sclay.size.height, 1.0f);
sgl_disable_texture();
sgl_push_pipeline();
sgl_load_pipeline(_sclay.pip);
for (uint32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i);
Clay_BoundingBox bbox = renderCommand->boundingBox;
switch (renderCommand->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
sgl_c4f(config->backgroundColor.r / 255.0f,
config->backgroundColor.g / 255.0f,
config->backgroundColor.b / 255.0f,
config->backgroundColor.a / 255.0f);
Clay_CornerRadius r = config->cornerRadius;
sgl_begin_triangle_strip();
if(r.topLeft > 0 || r.topRight > 0){
_draw_corner(bbox.x, bbox.y, -r.topLeft, -r.topLeft);
_draw_corner(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight);
_draw_rect(bbox.x+r.topLeft, bbox.y,
bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight));
}
if(r.bottomLeft > 0 || r.bottomRight > 0){
_draw_corner(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft);
_draw_corner(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight);
_draw_rect(bbox.x+r.bottomLeft,
bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight),
bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight));
}
if(r.topLeft < r.bottomLeft){
if(r.topLeft < r.topRight){
_draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft);
_draw_rect(bbox.x+r.topLeft, bbox.y+r.topRight,
r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft);
} else {
_draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft);
}
} else {
if(r.bottomLeft < r.bottomRight){
_draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft);
_draw_rect(bbox.x+r.bottomLeft, bbox.y+r.topLeft,
r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight);
} else {
_draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft);
}
}
if(r.topRight < r.bottomRight){
if(r.topRight < r.topLeft){
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft,
r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight);
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
r.topRight, bbox.height-r.topRight-r.bottomRight);
} else {
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight,
r.bottomRight, bbox.height-r.topRight-r.bottomRight);
}
} else {
if(r.bottomRight < r.bottomLeft){
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft);
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight,
r.bottomRight, bbox.height-r.topRight-r.bottomRight);
} else {
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
r.topRight, bbox.height-r.topRight-r.bottomRight);
}
}
_draw_rect(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft),
bbox.y+CLAY__MAX(r.topLeft, r.topRight),
bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight),
bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight));
sgl_end();
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
if(!fonts) break;
Clay_TextRenderData *config = &renderCommand->renderData.text;
Clay_StringSlice text = config->stringContents;
fonsSetFont(_sclay.fonts, fonts[config->fontId]);
uint32_t color = sfons_rgba(
config->textColor.r,
config->textColor.g,
config->textColor.b,
config->textColor.a);
fonsSetColor(_sclay.fonts, color);
fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale);
fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP);
fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale);
sgl_matrix_mode_modelview();
sgl_push_matrix();
sgl_scale(1.0f/_sclay.dpi_scale, 1.0f/_sclay.dpi_scale, 1.0f);
fonsDrawText(_sclay.fonts, bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale,
text.chars, text.chars + text.length);
sgl_pop_matrix();
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
sgl_scissor_rectf(bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale,
bbox.width*_sclay.dpi_scale, bbox.height*_sclay.dpi_scale,
true);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
sgl_scissor_rectf(0, 0,
_sclay.size.width*_sclay.dpi_scale, _sclay.size.height*_sclay.dpi_scale,
true);
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
//TODO
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData *config = &renderCommand->renderData.border;
sgl_c4f(config->color.r / 255.0f,
config->color.g / 255.0f,
config->color.b / 255.0f,
config->color.a / 255.0f);
Clay_BorderWidth w = config->width;
Clay_CornerRadius r = config->cornerRadius;
sgl_begin_triangle_strip();
if(w.left > 0){
_draw_rect(bbox.x, bbox.y + r.topLeft,
w.left, bbox.height - r.topLeft - r.bottomLeft);
}
if(w.right > 0){
_draw_rect(bbox.x + bbox.width - w.right, bbox.y + r.topRight,
w.right, bbox.height - r.topRight - r.bottomRight);
}
if(w.top > 0){
_draw_rect(bbox.x + r.topLeft, bbox.y,
bbox.width - r.topLeft - r.topRight, w.top);
}
if(w.bottom > 0){
_draw_rect(bbox.x + r.bottomLeft, bbox.y + bbox.height - w.bottom,
bbox.width - r.bottomLeft - r.bottomRight, w.bottom);
}
if(r.topLeft > 0 && (w.top > 0 || w.left > 0)){
_draw_corner_border(bbox.x, bbox.y,
-r.topLeft, -r.topLeft,
-r.topLeft+w.left, -r.topLeft+w.top);
}
if(r.topRight > 0 && (w.top > 0 || w.right > 0)){
_draw_corner_border(bbox.x+bbox.width, bbox.y,
r.topRight, -r.topRight,
r.topRight-w.right, -r.topRight+w.top);
}
if(r.bottomLeft > 0 && (w.bottom > 0 || w.left > 0)){
_draw_corner_border(bbox.x, bbox.y+bbox.height,
-r.bottomLeft, r.bottomLeft,
-r.bottomLeft+w.left, r.bottomLeft-w.bottom);
}
if(r.bottomRight > 0 && (w.bottom > 0 || w.right > 0)){
_draw_corner_border(bbox.x+bbox.width, bbox.y+bbox.height,
r.bottomRight, r.bottomRight,
r.bottomRight-w.right, r.bottomRight-w.bottom);
}
sgl_end();
break;
}
default:
break;
}
}
sgl_pop_pipeline();
sfons_flush(_sclay.fonts);
}
#endif /* SOKOL_CLAY_IMPL */

View File

@ -0,0 +1,5 @@
The windows GDI renderer example is missing the following:
- Images
- Rendering Rounded Rectangle borders
- Custom Fonts (font size)

View File

@ -0,0 +1,609 @@
#include <Windows.h>
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
#include <immintrin.h> // AVX intrinsincs for faster sqrtf
#endif
#include "../../clay.h"
HDC renderer_hdcMem = {0};
HBITMAP renderer_hbmMem = {0};
HANDLE renderer_hOld = {0};
DWORD g_dwGdiRenderFlags;
#ifndef RECTWIDTH
#define RECTWIDTH(rc) ((rc).right - (rc).left)
#endif
#ifndef RECTHEIGHT
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)
#endif
// Renderer options bit flags
// RF clearly stated in the name to avoid confusion with possible macro definitions for other purposes
#define CLAYGDI_RF_ALPHABLEND 0x00000001
#define CLAYGDI_RF_SMOOTHCORNERS 0x00000002
// These are bitflags, not indexes. Next would be 0x00000004
inline DWORD Clay_Win32_GetRendererFlags() { return g_dwGdiRenderFlags; }
// Replaces the rendering flags with new ones provided
inline void Clay_Win32_SetRendererFlags(DWORD dwFlags) { g_dwGdiRenderFlags = dwFlags; }
// Returns `true` if flags were modified
inline bool Clay_Win32_ModifyRendererFlags(DWORD dwRemove, DWORD dwAdd)
{
DWORD dwSavedFlags = g_dwGdiRenderFlags;
DWORD dwNewFlags = (dwSavedFlags & ~dwRemove) | dwAdd;
if (dwSavedFlags == dwNewFlags)
return false;
Clay_Win32_SetRendererFlags(dwNewFlags);
return true;
}
/*----------------------------------------------------------------------------+
| Math stuff start |
+----------------------------------------------------------------------------*/
// Intrinsincs wrappers
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
inline float intrin_sqrtf(const float f)
{
__m128 temp = _mm_set_ss(f);
temp = _mm_sqrt_ss(temp);
return _mm_cvtss_f32(temp);
}
#endif
// Use fast inverse square root
#if defined(USE_FAST_SQRT)
float fast_inv_sqrtf(float number)
{
const float threehalfs = 1.5f;
float x2 = number * 0.5f;
float y = number;
// Evil bit-level hacking
uint32_t i = *(uint32_t*)&y;
i = 0x5f3759df - (i >> 1); // Initial guess for Newton's method
y = *(float*)&i;
// One iteration of Newton's method
y = y * (threehalfs - (x2 * y * y)); // y = y * (1.5 - 0.5 * x * y^2)
return y;
}
// Fast square root approximation using the inverse square root
float fast_sqrtf(float number)
{
if (number < 0.0f) return 0.0f; // Handle negative input
return number * fast_inv_sqrtf(number);
}
#endif
// sqrtf_impl implementation chooser
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
#define sqrtf_impl(x) intrin_sqrtf(x)
#elif defined(USE_FAST_SQRT)
#define sqrtf_impl(x) fast_sqrtf(x)
#else
#define sqrtf_impl(x) sqrtf(x) // Fallback to std sqrtf
#endif
/*----------------------------------------------------------------------------+
| Math stuff end |
+----------------------------------------------------------------------------*/
static inline Clay_Color ColorBlend(Clay_Color base, Clay_Color overlay, float factor)
{
Clay_Color blended;
// Normalize alpha values for multiplications
float base_a = base.a / 255.0f;
float overlay_a = overlay.a / 255.0f;
overlay_a *= factor;
float out_a = overlay_a + base_a * (1.0f - overlay_a);
// Avoid division by zero and fully transparent cases
if (out_a <= 0.0f)
{
return (Clay_Color) { .a = 0, .r = 0, .g = 0, .b = 0 };
}
blended.r = (overlay.r * overlay_a + base.r * base_a * (1.0f - overlay_a)) / out_a;
blended.g = (overlay.g * overlay_a + base.g * base_a * (1.0f - overlay_a)) / out_a;
blended.b = (overlay.b * overlay_a + base.b * base_a * (1.0f - overlay_a)) / out_a;
blended.a = out_a * 255.0f; // Denormalize alpha back
return blended;
}
static float RoundedRectPixelCoverage(int x, int y, const Clay_CornerRadius radius, int width, int height) {
// Check if the pixel is in one of the four rounded corners
if (x < radius.topLeft && y < radius.topLeft) {
// Top-left corner
float dx = radius.topLeft - x - 1;
float dy = radius.topLeft - y - 1;
float distance = sqrtf_impl(dx * dx + dy * dy);
if (distance > radius.topLeft)
return 0.0f;
if (distance <= radius.topLeft - 1)
return 1.0f;
return radius.topLeft - distance;
}
else if (x >= width - radius.topRight && y < radius.topRight) {
// Top-right corner
float dx = x - (width - radius.topRight);
float dy = radius.topRight - y - 1;
float distance = sqrtf_impl(dx * dx + dy * dy);
if (distance > radius.topRight)
return 0.0f;
if (distance <= radius.topRight - 1)
return 1.0f;
return radius.topRight - distance;
}
else if (x < radius.bottomLeft && y >= height - radius.bottomLeft) {
// Bottom-left corner
float dx = radius.bottomLeft - x - 1;
float dy = y - (height - radius.bottomLeft);
float distance = sqrtf_impl(dx * dx + dy * dy);
if (distance > radius.bottomLeft)
return 0.0f;
if (distance <= radius.bottomLeft - 1)
return 1.0f;
return radius.bottomLeft - distance;
}
else if (x >= width - radius.bottomRight && y >= height - radius.bottomRight) {
// Bottom-right corner
float dx = x - (width - radius.bottomRight);
float dy = y - (height - radius.bottomRight);
float distance = sqrtf_impl(dx * dx + dy * dy);
if (distance > radius.bottomRight)
return 0.0f;
if (distance <= radius.bottomRight - 1)
return 1.0f;
return radius.bottomRight - distance;
}
else {
// Not in a corner, full coverage
return 1.0f;
}
}
typedef struct {
HDC hdcMem;
HBITMAP hbmMem;
HBITMAP hbmMemPrev;
void* pBits;
SIZE size;
} HDCSubstitute;
static void CreateHDCSubstitute(HDCSubstitute* phdcs, HDC hdcSrc, PRECT prc)
{
if (prc == NULL)
return;
phdcs->size = (SIZE){ RECTWIDTH(*prc), RECTHEIGHT(*prc) };
if (phdcs->size.cx <= 0 || phdcs->size.cy <= 0)
return;
phdcs->hdcMem = CreateCompatibleDC(hdcSrc);
if (phdcs->hdcMem == NULL)
return;
// Create a 32-bit DIB section for the memory DC
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = phdcs->size.cx;
bmi.bmiHeader.biHeight = -phdcs->size.cy; // I think it's faster? Probably
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
phdcs->pBits = NULL;
phdcs->hbmMem = CreateDIBSection(phdcs->hdcMem, &bmi, DIB_RGB_COLORS, &phdcs->pBits, NULL, 0);
if (phdcs->hbmMem == NULL)
{
DeleteDC(phdcs->hdcMem);
return;
}
// Select the DIB section into the memory DC
phdcs->hbmMemPrev = SelectObject(phdcs->hdcMem, phdcs->hbmMem);
// Copy the content of the target DC to the memory DC
BitBlt(phdcs->hdcMem, 0, 0, phdcs->size.cx, phdcs->size.cy, hdcSrc, prc->left, prc->top, SRCCOPY);
}
static void DestroyHDCSubstitute(HDCSubstitute* phdcs)
{
if (phdcs == NULL)
return;
// Clean up
SelectObject(phdcs->hdcMem, phdcs->hbmMemPrev);
DeleteObject(phdcs->hbmMem);
DeleteDC(phdcs->hdcMem);
ZeroMemory(phdcs, sizeof(HDCSubstitute));
}
static void __Clay_Win32_FillRoundRect(HDC hdc, PRECT prc, Clay_Color color, Clay_CornerRadius radius)
{
HDCSubstitute substitute = { 0 };
CreateHDCSubstitute(&substitute, hdc, prc);
bool has_corner_radius = radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
if (has_corner_radius)
{
// Limit the corner radius to the minimum of half the width and half the height
float max_radius = (float)fmin(substitute.size.cx / 2.0f, substitute.size.cy / 2.0f);
if (radius.topLeft > max_radius) radius.topLeft = max_radius;
if (radius.topRight > max_radius) radius.topRight = max_radius;
if (radius.bottomLeft > max_radius) radius.bottomLeft = max_radius;
if (radius.bottomRight > max_radius) radius.bottomRight = max_radius;
}
// Iterate over each pixel in the DIB section
uint32_t* pixels = (uint32_t*)substitute.pBits;
for (int y = 0; y < substitute.size.cy; ++y)
{
for (int x = 0; x < substitute.size.cx; ++x)
{
float coverage = 1.0f;
if (has_corner_radius)
coverage = RoundedRectPixelCoverage(x, y, radius, substitute.size.cx, substitute.size.cy);
if (coverage > 0.0f)
{
uint32_t pixel = pixels[y * substitute.size.cx + x];
Clay_Color dst_color = {
.r = (float)((pixel >> 16) & 0xFF), // Red
.g = (float)((pixel >> 8) & 0xFF), // Green
.b = (float)(pixel & 0xFF), // Blue
.a = 255.0f // Fully opaque
};
Clay_Color blended = ColorBlend(dst_color, color, coverage);
pixels[y * substitute.size.cx + x] =
((uint32_t)(blended.b) << 0) |
((uint32_t)(blended.g) << 8) |
((uint32_t)(blended.r) << 16);
}
}
}
// Copy the blended content back to the target DC
BitBlt(hdc, prc->left, prc->top, substitute.size.cx, substitute.size.cy, substitute.hdcMem, 0, 0, SRCCOPY);
DestroyHDCSubstitute(&substitute);
}
void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands, HFONT* fonts)
{
bool is_clipping = false;
HRGN clipping_region = {0};
PAINTSTRUCT ps;
HDC hdc;
RECT rc; // Top left of our window
GetWindowRect(hwnd, &rc);
hdc = BeginPaint(hwnd, &ps);
int win_width = rc.right - rc.left,
win_height = rc.bottom - rc.top;
// Create an off-screen DC for double-buffering
renderer_hdcMem = CreateCompatibleDC(hdc);
renderer_hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
renderer_hOld = SelectObject(renderer_hdcMem, renderer_hbmMem);
// draw
for (int j = 0; j < renderCommands.length; j++)
{
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
switch (renderCommand->commandType)
{
case CLAY_RENDER_COMMAND_TYPE_TEXT:
{
Clay_Color c = renderCommand->renderData.text.textColor;
SetTextColor(renderer_hdcMem, RGB(c.r, c.g, c.b));
SetBkMode(renderer_hdcMem, TRANSPARENT);
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width + r.right;
r.bottom = boundingBox.y + boundingBox.height + r.bottom;
uint16_t font_id = renderCommand->renderData.text.fontId;
HFONT hFont = fonts[font_id];
HFONT hPrevFont = SelectObject(renderer_hdcMem, hFont);
// Actually draw text
DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars,
renderCommand->renderData.text.stringContents.length,
&r, DT_TOP | DT_LEFT);
SelectObject(renderer_hdcMem, hPrevFont);
break;
}
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
{
DWORD dwFlags = Clay_Win32_GetRendererFlags();
Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle;
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height;
bool translucid = false;
// There is need to check that only if alphablending is enabled.
// In other case the blending will be always opaque and we can jump to simpler FillRgn/Rect
if (dwFlags & CLAYGDI_RF_ALPHABLEND)
translucid = rrd.backgroundColor.a > 0.0f && rrd.backgroundColor.a < 255.0f;
bool has_rounded_corners = rrd.cornerRadius.topLeft > 0.0f
|| rrd.cornerRadius.topRight > 0.0f
|| rrd.cornerRadius.bottomLeft > 0.0f
|| rrd.cornerRadius.bottomRight > 0.0f;
// We go here if CLAYGDI_RF_SMOOTHCORNERS flag is set and one of the corners is rounded
// Also we go here if GLAYGDI_RF_ALPHABLEND flag is set and the fill color is translucid
if ((dwFlags & CLAYGDI_RF_ALPHABLEND) && translucid || (dwFlags & CLAYGDI_RF_SMOOTHCORNERS) && has_rounded_corners)
{
__Clay_Win32_FillRoundRect(renderer_hdcMem, &r, rrd.backgroundColor, rrd.cornerRadius);
}
else
{
HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b));
if (has_rounded_corners)
{
HRGN roundedRectRgn = CreateRoundRectRgn(
r.left, r.top, r.right + 1, r.bottom + 1,
rrd.cornerRadius.topLeft * 2, rrd.cornerRadius.topLeft * 2);
FillRgn(renderer_hdcMem, roundedRectRgn, recColor);
DeleteObject(roundedRectRgn);
}
else
{
FillRect(renderer_hdcMem, &r, recColor);
}
DeleteObject(recColor);
}
break;
}
// The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox.
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
{
is_clipping = true;
clipping_region = CreateRectRgn(boundingBox.x,
boundingBox.y,
boundingBox.x + boundingBox.width,
boundingBox.y + boundingBox.height);
SelectClipRgn(renderer_hdcMem, clipping_region);
break;
}
// The renderer should finish any previously active clipping, and begin rendering elements in full again.
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
{
SelectClipRgn(renderer_hdcMem, NULL);
if (clipping_region)
{
DeleteObject(clipping_region);
}
is_clipping = false;
clipping_region = NULL;
break;
}
// The renderer should draw a colored border inset into the bounding box.
case CLAY_RENDER_COMMAND_TYPE_BORDER:
{
Clay_BorderRenderData brd = renderCommand->renderData.border;
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height;
HPEN topPen = CreatePen(PS_SOLID, brd.width.top, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN leftPen = CreatePen(PS_SOLID, brd.width.left, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN bottomPen = CreatePen(PS_SOLID, brd.width.bottom, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN rightPen = CreatePen(PS_SOLID, brd.width.right, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN oldPen = SelectObject(renderer_hdcMem, topPen);
if (brd.cornerRadius.topLeft == 0)
{
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.top);
SelectObject(renderer_hdcMem, leftPen);
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.left, r.bottom);
SelectObject(renderer_hdcMem, bottomPen);
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
SelectObject(renderer_hdcMem, rightPen);
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
}
else
{
// todo: i should be rounded
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.top);
SelectObject(renderer_hdcMem, leftPen);
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.left, r.bottom);
SelectObject(renderer_hdcMem, bottomPen);
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
SelectObject(renderer_hdcMem, rightPen);
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
}
SelectObject(renderer_hdcMem, oldPen);
DeleteObject(topPen);
DeleteObject(leftPen);
DeleteObject(bottomPen);
DeleteObject(rightPen);
break;
}
// case CLAY_RENDER_COMMAND_TYPE_IMAGE:
// {
// // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :(
// break;
// }
default:
printf("Unhandled render command %d\r\n", renderCommand->commandType);
break;
}
}
BitBlt(hdc, 0, 0, win_width, win_height, renderer_hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(renderer_hdcMem, renderer_hOld);
DeleteObject(renderer_hbmMem);
DeleteDC(renderer_hdcMem);
EndPaint(hwnd, &ps);
}
/*
Hacks due to the windows api not making sence to use.... may measure too large, but never too small
*/
#ifndef WIN32_FONT_HEIGHT
#define WIN32_FONT_HEIGHT (16)
#endif
#ifndef WIN32_FONT_WIDTH
#define WIN32_FONT_WIDTH (8)
#endif
static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
{
Clay_Dimensions textSize = {0};
if (userData != NULL)
{
HFONT* fonts = (HFONT*)userData;
HFONT hFont = fonts[config->fontId];
if (hFont != NULL)
{
HDC hScreenDC = GetDC(NULL);
HDC hTempDC = CreateCompatibleDC(hScreenDC);
if (hTempDC != NULL)
{
HFONT hPrevFont = SelectObject(hTempDC, hFont);
SIZE size;
GetTextExtentPoint32(hTempDC, text.chars, text.length, &size);
textSize.width = size.cx;
textSize.height = size.cy;
SelectObject(hScreenDC, hPrevFont);
DeleteDC(hTempDC);
return textSize;
}
ReleaseDC(HWND_DESKTOP, hScreenDC);
}
}
// Fallback for system bitmap font
float maxTextWidth = 0.0f;
float lineTextWidth = 0;
float textHeight = WIN32_FONT_HEIGHT;
for (int i = 0; i < text.length; ++i)
{
if (text.chars[i] == '\n')
{
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
lineTextWidth = 0;
continue;
}
lineTextWidth += WIN32_FONT_WIDTH;
}
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
textSize.width = maxTextWidth;
textSize.height = textHeight;
return textSize;
}
HFONT Clay_Win32_SimpleCreateFont(const char* filePath, const char* family, int height, int weight)
{
// Add the font resource to the application instance
int fontAdded = AddFontResourceEx(filePath, FR_PRIVATE, NULL);
if (fontAdded == 0) {
return NULL;
}
int fontHeight = height;
// If negative, treat height as Pt rather than pixels
if (height < 0) {
// Get the screen DPI
HDC hScreenDC = GetDC(NULL);
int iScreenDPI = GetDeviceCaps(hScreenDC, LOGPIXELSY);
ReleaseDC(HWND_DESKTOP, hScreenDC);
// Convert font height from points to pixels
fontHeight = MulDiv(height, iScreenDPI, 72);
}
// Create the font using the calculated height and the font name
HFONT hFont = CreateFont(fontHeight, 0, 0, 0, weight, FALSE, FALSE, FALSE,
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH, family);
return hFont;
}