Compare commits

..

332 commits
v0.11 ... main

Author SHA1 Message Date
github-actions[bot] 37675089e3 [bindings/odin] Update Odin bindings 2025-09-16 01:40:04 +00:00
Nic Barker 1cbc56cbf2
[Core] Remove ID from element declaration struct and split clay macro into CLAY and CLAY_WITHID (#492) 2025-09-16 11:39:07 +10:00
github-actions[bot] 0d6969c8c3 [bindings/odin] Update Odin bindings 2025-09-08 02:15:33 +00:00
Víctor López 958d684b3c
[Compilers] Fix struct with default initializer not using CLAY__DEFAULT_STRUCT (#498) 2025-09-08 12:14:05 +10:00
Rats f55513493b
[Bindings/Odin] Fix ID procedure ignoring index (#496) 2025-09-08 12:13:00 +10:00
github-actions[bot] dabf8214a8 [bindings/odin] Update Odin bindings 2025-09-08 02:12:08 +00:00
fgungor 747643e84e
[Core] fix unused extra macro param in CLAY_SID_LOCAL (#505) 2025-09-08 12:10:10 +10:00
rivten 5a0d301c60
[Renderers/Sokol] allow usage of images with sokol renderer (#489)
Co-authored-by: Hugo Viala <h.viala@ganacos.com>
Co-authored-by: Andrew Sampson <1297077+andrewmd5@users.noreply.github.com>
2025-09-08 11:35:18 +10:00
Andrew Sampson 1bc5105272
[Renderers/Sokol] Text measurement/rendering width mismatch in sokol_clay (#480) 2025-08-19 12:14:13 +10:00
github-actions[bot] 61bab7bba6 [bindings/odin] Update Odin bindings 2025-08-14 23:38:20 +00:00
Nic Barker 7f767d2301 [Core] Increase the default scroll container limit to 100 2025-08-15 09:37:23 +10:00
Daniel S Jeremiah 2b192409b9
[Documentation] -- updated README (#475) 2025-08-14 10:02:23 +10:00
github-actions[bot] 91c6d05774 [bindings/odin] Update Odin bindings 2025-07-15 23:32:23 +00:00
Nic Barker 4aa3d75bef [Core] Split base ID hash from index ID hash 2025-07-16 09:30:54 +10:00
Hayden Gray 9d38edb989
[Bindings/Odin] SizingFit and SizingGrow default parameters (#462) 2025-07-09 09:07:10 +10:00
github-actions[bot] 516e85bdfe [bindings/odin] Update Odin bindings 2025-07-01 23:47:56 +00:00
Tomás Ralph 0022d12c0c
[DebugTools] Fix aspect ratio display, closes #449 (#451) 2025-07-02 09:46:12 +10:00
Nic Barker d9d0b6c37b [Core] Fix incorrect percentage sizing of floating elements 2025-07-02 09:37:38 +10:00
Hayden Gray 8dfcc944fa
[Bindings/Odin] - add CI to update binding libs (#452) 2025-06-27 15:05:13 +10:00
Mivirl a9c1f9a8a7
[Renderers/termbox2] Termbox2 renderer & examples (#419) 2025-06-27 08:26:38 +10:00
Nic Barker 281f961e3d [Bindings/Odin] Update Odin bindings 2025-06-24 13:57:15 +10:00
Nic Barker 3433a53a8e [Core] Fix an alignment bug for scrolling container contents 2025-06-24 13:55:20 +10:00
wolfee 4a91cac1b2
[Renderers/SDL3] Fix border position calculated incorrectly (#446) 2025-06-23 10:43:08 +10:00
Nic Barker ff7917798c [Core] Avoid calling measure text function with length zero 2025-06-23 10:10:13 +10:00
wolfee f97cb9ea10
[Renderers/SDL] Font sizing is ignored (#444) 2025-06-23 09:30:14 +10:00
Nic Barker 74f0ffbe26 [Renderers/Cairo] Fix outdated image API usage in cairo example 2025-06-23 09:16:41 +10:00
Nic Barker a5e212b8de [Renderers/HTML] Fix a mouse overlap bug with external scroll handling 2025-06-20 10:59:44 +10:00
Nic Barker 0835781602 [Bindings/Odin] Update Odin Bindings 2025-06-18 09:55:32 +10:00
Nic Barker 13ecd80ee5 [Renderers/SDL3] Fix up outdated API usage in SDL3 example 2025-06-18 09:54:27 +10:00
Nic Barker c524485c46 [Core] Fix case where there could be 64 extra bytes of padding between arrays in clays internal arena 2025-06-17 13:07:56 +10:00
Nic Barker b9e27178c0 [Core] Align base arena memory to 64 byte cache line 2025-06-17 10:32:30 +10:00
Nic Barker 6f10bf4b3d [Core] Remove erroneous break statement when using external scroll handling 2025-06-13 10:35:31 +10:00
Nic Barker adb1bd620a [Examples/clay-official-website] Fix links on official website example 2025-06-13 09:59:07 +10:00
Nic Barker dca9f60a6c [Core] Add objective-c support 2025-06-12 12:16:29 +10:00
Nic Barker 3ccfa0f8fa [Core] Fix a bug where extra child gap was added to the dimensions of clipped containers 2025-06-11 10:41:01 +10:00
Nic Barker 35b45d939b Fix some typos in the readme 2025-06-11 10:15:51 +10:00
Nic Barker 6b03a215b7 [Core] Fix an out of bounds access for single newline characters 2025-06-10 11:38:56 +10:00
Nic Barker b25a31c1a1 [Bindings/Odin] Update Odin Bindings to latest 2025-06-06 11:01:42 +10:00
Nic Barker 8bbe14fbcc [Documentation] Fix README typo, bump version number to 0.14 2025-06-06 10:20:37 +10:00
Alan 58491394ca
[Renderers/Raylib] Fix element float distortion (#430) 2025-06-05 10:49:26 +10:00
Nic Barker 87575cb7c3 [Core] Fix handling of letter spacing 2025-06-05 10:38:53 +10:00
Nic Barker 80659eda04 [Renderers/Web] Fix clay official website example struct definitions for web renderers 2025-06-04 10:50:35 +10:00
Rats a3003cfa12
[Bindings/Odin] Update README to better match official website example. (#422) 2025-06-02 20:26:04 +10:00
Wes Lord 5eceb52abc
Set CMake FetchContent GIT_TAG for SDL_ttf (#423) 2025-06-02 13:00:45 +10:00
Michael Tanner 149833bdc0
[Core] Fix dimension calculation that would always result in 0 (#428) 2025-06-02 12:27:28 +10:00
Nic Barker 0431f862f4 [Core] Improve handling of aspect ratio scaling 2025-06-02 12:14:09 +10:00
Nic Barker d6f3957a60
[Core] Split aspect ratio scaling into its own config (#426) 2025-06-02 10:36:58 +10:00
Boreal 89ce22e894
[Core] Fix sign comparison warning (#427) 2025-05-29 13:10:05 +10:00
Wes Lord e80736892d
[Renderers/SDL2] Indent SDL2's CMakeLists.txt consistently (#424) 2025-05-28 09:50:00 +10:00
Nic Barker ce2475ba73 [Compilers] Add missing struct name for image element config 2025-05-26 10:10:24 +10:00
Johann Muszynski 34ff7e1bea
[Compilers] Add struct names to public structs (#336) 2025-05-26 10:03:07 +10:00
Emmanuel 65e813d4df
[Renderers/Terminal] Add initial implementation of terminal renderer (#91) 2025-05-22 12:45:52 +12:00
Nic Barker 7af50d0f48 [Bindings/Odin] Update odin bindings to include floating element clipTo 2025-05-20 11:40:25 +12:00
Matt Jennings e2f94f17f6
[Renderers/Playdate] Playdate console example (#404) 2025-05-19 11:46:39 +12:00
Zordan eb1d85f2a6
[Renderers/Cairo] Fix cairo renderer and example (#416) 2025-05-19 11:36:08 +12:00
Patricio Whittingslow 76351a0999
[Documentation] Add Go rewrites of clay to README (#411) 2025-05-19 11:33:38 +12:00
Simon Oelerich efbd680685
[Core] restore compatibility with C99 (#412) 2025-05-14 13:29:12 +12:00
Patrick Doane b656dc5253
[Core] Add Clay_FloatingClipToElement (#413) 2025-05-14 13:24:42 +12:00
João Matos b78fd66da8
[Core] Add Clay_GetPointerOverIds function to the public API. (#389) 2025-05-14 12:39:56 +12:00
Jeroen van Rijn 76265e4c3c
[Bindings/Odin] Add missing border macros to Odin bindings 2025-05-09 13:25:34 +12:00
Sam El-Borai c483269295
[Documentation] Fix anchor to Clay_CustomElementConfig (#403) 2025-05-06 12:41:33 +12:00
Nic Barker 4aad9daa9e [Documentation] Update README 2025-05-06 12:33:55 +12:00
João Matos 52759cd028
[Debug] Update Clay__RenderDebugLayoutSizing to handle more sizing types. (#392) 2025-05-05 12:27:55 +12:00
David Delassus fb4eec93b2
[Renderers/SDL3] Use SDL_Texture instead of SDL_Surface for images (#402) 2025-05-05 12:27:41 +12:00
Hayden Gray e4e7b113a9
[Bindings/Odin] Updated odin bindings with new clip config (#397)
Great work, thanks for moving on this so quickly! 😁
2025-05-02 16:02:40 +12:00
Nic Barker 90b45f059c [Core] Fix a bug with the implementation of clip .childOffset 2025-05-01 17:21:09 +12:00
Rats ea8288158e
[Bindings/Odin] Odin Raylib renderer rewrite (#395) 2025-05-01 14:31:05 +12:00
Nic Barker 970919e1fb
[Core] Replace .scroll config with .clip (#376) 2025-05-01 14:11:31 +12:00
Nic Barker 313964132c [Renderers/Raylib] Pin raylib version to 5.5 2025-04-29 12:43:44 +12:00
Nic Barker a21b0665fe [Core] Correctly throw an error when using attach to element id with an invalid id 2025-04-29 12:31:54 +12:00
Nic Barker b33ba4ff62
[Core] Fix a string hash bug with single characters (#384) 2025-04-16 20:16:05 +12:00
Jackson Novak f88f0517f7
[Documentation] Fix Clay_String definition in README.md file. (#374) 2025-04-16 20:07:16 +12:00
Tim Lügger 5391a259f3
[Renderers/Raylib] Fix raylib renderer border bottom left corner radius (#378) 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 2025-04-12 11:27:10 +12:00
Nathan Korth eb46688b82
[Renderers/Sokol] Sokol renderer & examples (#373) 2025-04-09 13:40:22 +12:00
Philosoph228 87efc49f52
[Renderers/WinGDI] Working on Win32 GDI renderer and example (#344) 2025-04-09 11:31:33 +12:00
Nic Barker a9e94e3be0 [Core] Fix onHover reference not being reset for identical IDs between frames 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) 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 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 2025-03-27 10:14:17 +13:00
Nic Barker 08e4c5b198 [Core] Fix a bug where ID aliases werent copied on hash collision 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) 2025-03-25 10:13:04 +13:00
Nic Barker ad49977f1b [Core] Apply minimum width for single words and fix some minimum sizing bugs 2025-03-21 11:22:48 +13:00
Leo Zurbriggen 61490e4557
[Bindings/Odin] expose _OpenElement and _CloseElement (#301) 2025-03-21 09:25:50 +13:00
Nic Barker 982ade4cf9 [Compilers] Add a dummy function to suppress unused variable warning in GCC 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 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
[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 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) 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 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) 2025-03-07 21:45:27 +13:00
Nic Barker 5009146c65 [Bindings/Odin] Recompile odin bindings with -O3 2025-03-05 15:11:11 +13:00
Rico P 865b06d386
[Documentation] fix example in README (#307) 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) 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 2025-03-03 11:36:12 +13:00
Joram Vandemoortele 4ee501019c
[Compilers] Added DLL macro to support .dll building (#278) 2025-02-26 15:37:51 +13:00
Nic Barker 1fa8684e47 [Core] Fix bug where hover state didnt take clip rectangles into account 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) 2025-02-24 08:52:41 +13:00
mizmar 766325c395
[Core] Fix inverted condition for setting booleanWarnings.maxTextMeasureCacheExceeded (#275) 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 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) 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 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) 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
Nic Barker 6d23a35d15 [Examples/clay-official-website] Update compiled wasm for official website example 2025-02-12 13:09:29 +13:00
Nic Barker b4933a6e4c [Examples/clay-official-website] Switch default renderer back to HTML for official website example 2025-02-12 13:08:46 +13:00
Nic Barker 9f91450431 [Bindings/Odin] Update odin bindings to include debug tools changes 2025-02-12 13:07:05 +13:00
Nic Barker e35bba079e [Core] Update debug tools to include text alignment 2025-02-12 13:05:48 +13:00
Nic Barker d637e2a122 [Documentation] Fix documentation mistake for border configuration 2025-02-12 12:02:57 +13:00
Nic Barker e6e0cd5a46 [Documentation] Update README with better documentation of Clay_ElementDeclaration 2025-02-12 11:59:50 +13:00
Nic Barker 82ca328ae2 [Core] Add .textAlignment field to text element config 2025-02-12 10:43:32 +13:00
Nic Barker 72af2a4020 [Compilers] Fix missing initializer warnings under -Wextra 2025-02-12 09:12:11 +13:00
Nic Barker 3961720ef0 [Core & Documentation] Cleanup public / private API and internal document public API via comments 2025-02-11 17:11:03 +13:00
Nic Barker dd1f018444 [Documentation] Add inline documentation comments for subfields of Clay_ElementDeclaration 2025-02-11 14:14:55 +13:00
Nic Barker 5a328da308 [Bindings/Odin] Switch error enum to correct size 2025-02-11 10:51:10 +13:00
Harrison Lambeth 3030390038
Define CLAY_IMPLEMENTATION in Jetbrains IDE (#236) 2025-02-11 10:11:42 +13:00
Nic Barker 92582f66d8 [DebugTools] Fix a bug with display of border widths in debug tools 2025-02-11 10:11:15 +13:00
Nic Barker 65d2122dd6 [Core] Fix a bug where floating containers with anonymous IDs could conflict 2025-02-11 10:09:17 +13:00
FelixBreitweiser fd76ce62f3
[Core] Check whether the maximum number of elements has been exceeded before rendering the debug view (#255) 2025-02-11 09:35:51 +13:00
Joram Vandemoortele a5983dee96
Create csharp bindings README (#247) 2025-02-10 19:09:30 +13:00
Nic Barker 76c8e1f115 [Examples/clay-official-website] Update web renderer example to latest API 2025-02-10 16:53:21 +13:00
Nic Barker dcd6feda86 [Bindings/Odin] Add support for Clay_Hovered() to Odin bindings 2025-02-08 18:54:15 +13:00
Dan Korostelev b4102400ff
[Bindings/Odin] Add get/set current context method to Odin bindings (#252) 2025-02-08 18:51:55 +13:00
Nic Barker 5b0e5ea456 [Documentation] Update README to fix quick start example nesting 2025-02-07 11:34:10 +13:00
tomat e7bc3869f7
[Renderers/SDL2] Add rounded rectangle support to sdl2 renderer; feature-completes sdl2 renderer (#245) 2025-02-07 11:26:49 +13:00
Nic Barker ddc20bc8f6 [Core] Add error for incorrect percentage values, update Odin bindings enum formatting 2025-02-07 10:39:55 +13:00
Nic Barker 0a9122e78d [Bindings/odin] Update odin bindings for debug tooling 2025-02-07 10:24:57 +13:00
Nic Barker e97031f234 fix mistake in x64 simd comparison 2025-02-07 09:56:05 +13:00
johan0A 7a84facec9
add CLAY_DISABLE_SIMD flag to conditionally disable SIMD includes (#251) 2025-02-07 09:41:38 +13:00
Nic Barker bd2ce4b833 [Core] Update debug view for new non rectangle API 2025-02-07 09:34:48 +13:00
Nic Barker 0468243ac7 [Bindings/Odin] Update odin bindings for rendercommand changes 2025-02-06 19:02:54 +13:00
Nic Barker b9c5f8e47f [Core] Fixed a bug where userdata wasn't getting correctly passed through for image render commands 2025-02-06 10:06:10 +13:00
Nic Barker 95fcd85a2a [Core] Fixed a bug where Clay_Hovered didn't work with explicit IDs 2025-02-06 09:54:07 +13:00
Nic Barker 9d940c1f8e
[Core] Replace config macros with a single unified configuration struct (#240) 2025-02-04 17:00:19 +13:00
Harrison Lambeth 40ae6d8894
Fix int conversion errors in msvc (#242) 2025-01-30 15:46:37 +13:00
Harrison Lambeth efad3deef8
Copy elementId in Clay__AddHashMapItem() in case underlying stringId has changed (#239) 2025-01-30 10:20:14 +13:00
Nic Barker a1e692b72a
[Core] Add option to hash text contents to text config (#238) 2025-01-30 09:59:42 +13:00
Nic Barker 5fae7a6249 [Core] Compensate for OSes that don't return 64b aligned memory from malloc 2025-01-29 21:16:24 +13:00
Nic Barker 1bcf256e4d
[Core] Replace generated arrays with macro declarations, align cache lines to 64 bytes (#235) 2025-01-29 17:14:01 +13:00
Martin Evald e9f2e6c4f1
[Renderers/SDL2] Don't take addresses of temporaries. (#232) 2025-01-29 13:09:41 +13:00
noflashbang 34f2dab9e8
Normalized usage of Clay__defaultMaxElementCount and Clay__defaultMaxMeasureTextWordCacheCount (#233) 2025-01-29 13:09:07 +13:00
Nic Barker 951d785deb [Documentation] Fix incorrect type information in README for CLAY_IDI 2025-01-26 15:43:55 +13:00
Nic Barker 4612481d25 [Documentation] Fix some typos in the README example code 2025-01-26 15:35:30 +13:00
Nic Barker 0a703de69a
[Core] Add z-index and string base to Render Commands (#227) 2025-01-26 15:28:35 +13:00
Nic Barker cb62db77e3 [Documentation] Combine quick start steps into a single code block in main README 2025-01-26 15:22:46 +13:00
Cory ea6109bd0b
[CMake] Make Examples Optional in CMAKE (#216) 2025-01-26 15:05:45 +13:00
arnauNau c0dac38c87
[Renderers/SDL3] Add borders and rounded borders functionality. (#220) 2025-01-26 14:39:34 +13:00
Timothy Hoyt c3fcf6ce12
[Bindings/C++] Link and information for ClayMan, a C++ wrapper library for clay (#218) 2025-01-24 20:51:00 +13:00
arnauNau aba846a446
[Renderers/SDL3] Add rounded corners rectangle functionality (#219) 2025-01-23 09:30:24 +13:00
Nic Barker 9d659e8abd [Examples/Raylib] Restore deleted font 2025-01-21 21:28:38 +13:00
Nic Barker ec2b3b35ff [Renderers/Raylib] Early return 0 rather than segfault when Raylib fonts fail to load 2025-01-21 21:26:57 +13:00
Nic Barker 5f7176cdcc Fix quick start in README to include error handler 2025-01-21 21:17:24 +13:00
Nic Barker ebeef93c34 [Documentation] Include CLAY_ID_LOCAL in README.md 2025-01-21 21:11:49 +13:00
Nic Barker 9b2d585499 Update odin and wasm bindings 2025-01-21 19:14:22 +13:00
Nic Barker 81589ad29b [Core] Fix layout bug in SIZING_PERCENT 2025-01-21 19:11:33 +13:00
Nic Barker 16f894bb4d Fix incorrect use of corner radius 2025-01-21 18:32:33 +13:00
Timothy Hoyt 9f07f5aac8
fixed video demo padding (#205) 2025-01-21 18:31:48 +13:00
Nic Barker 01d3ab127f Update odin bindings for new text measurement API 2025-01-20 11:39:59 +13:00
Nic Barker 326325ffaf
[Core] Convert measureText pointer to value string slice (#214) 2025-01-20 11:27:22 +13:00
Daniel Collin e8025cc254
SetMesureText and SetQueryScrollOffset takes userData (#212) 2025-01-20 10:59:02 +13:00
Linus Probert 8e7e30dda6
[Renderers/SDL3] Adds an example using SDL3 as a renderer (#107)
Co-authored-by: Nic Barker <contact+github@nicbarker.com>
2025-01-19 14:35:41 +13:00
William Inal Zettergren 9d3fba39be
Add external link to zig bindings (#210) 2025-01-18 21:43:23 +13:00
ppeb 4961f2153e
Extend SDL2 Renderer and SDL2-video-demo (#208) 2025-01-18 21:42:18 +13:00
Nic Barker a093730da2 [Core] Fix a segfault if debug tools were enabled without a measure text function 2025-01-16 09:14:57 +13:00
Michael Savage cd82ce6fcf
[Core] Don't divide zero by zero (#200)
Co-authored-by: Nic Barker <contact+github@nicbarker.com>
2025-01-15 10:06:22 +13:00
Nic Barker 814c9392c6
[Core] Add API to query element bounding boxes (#199)
Co-authored-by: hexmaster111 <hailey@not-an-email-address.fake>
2025-01-14 22:09:06 +13:00
Nic Barker 338852b3ce Fix a bug with the new padding 2025-01-14 14:26:30 +13:00
Nic Barker f1d8a53a32
[Core] [Breaking] Split padding values into left, right, top, bottom (#195) 2025-01-14 10:38:02 +13:00
Harrison Lambeth afba9f0de6
Add a function to reset text measurement cache (#181) 2025-01-13 19:26:46 +13:00
Nic Barker 3a4455aa83
Fix text wrapping handling with explicit newline characters (#192)
Co-authored-by: Ryzee119 <wendland@live.com.au>
2025-01-13 19:23:28 +13:00
Nic Barker 208c7cb3a0 Fix incorrect border between children after 2nd element 2025-01-12 19:16:09 +13:00
Nic Barker c2c445e455 Fix broken ifdef on MSVC 2025-01-12 10:49:00 +13:00
Nic Barker 9e7595b873 Fixed a bug where minMemorySize could cause a memory overwrite 2025-01-11 21:37:21 +13:00
Nic Barker 32d1a31dfe Fix uint64 usage for wasm 2025-01-11 21:35:45 +13:00
Nic Barker b2b50724e2 Fix bug in html renderer debug tools 2025-01-11 20:45:20 +13:00
Nic Barker 12b3280dab update odin bindings 2025-01-11 14:22:52 +13:00
Nic Barker d81c9e1de5 fix C++ compile issues 2025-01-11 14:21:47 +13:00
Nic Barker 7142a427bb Update odin bindings 2025-01-11 14:19:31 +13:00
Nic Barker d7ee448ed5 Add EXTEND_CONFIG_BORDER 2025-01-11 14:15:24 +13:00
Nic Barker 7ecd5adbce Update debug view to have correct z indexing 2025-01-11 14:15:02 +13:00
Nic Barker 2fcb4cc76e Fix z index sorting of tree roots 2025-01-11 14:08:02 +13:00
Nic Barker 44fb89c8b6 Add an epsilon to compression comparison to prevent degenerate loop 2025-01-10 21:54:13 +13:00
Nic Barker bc9ef8b02d
Update README.md 2025-01-10 21:05:12 +13:00
Mathys Gasnier 0989aeee06
[Documentation] Summary & Readability improvement (#125) 2025-01-10 21:03:32 +13:00
Funto e11a394c25
[Compilers] Fix MSVC compilation with CMake (#178)
Co-authored-by: Nic Barker <contact+github@nicbarker.com>
2025-01-10 20:59:13 +13:00
FintasticMan 670f707997
[Core] Remove ##__VA_ARGS__ (#150) 2025-01-10 20:47:00 +13:00
Nic Barker b4452d080c C++ init order fix 2025-01-10 14:36:24 +13:00
Nic Barker 209f30dd56 Fix missing context initialization 2025-01-10 12:50:12 +13:00
Nic Barker 8efa855e8c [Compilers] Fixes for scrolling container example in MSVC 2025-01-10 10:31:29 +13:00
Nic Barker 83ded6995e Add compiler error when attempting to create CLAY_STRING with non literal 2025-01-10 09:20:35 +13:00
Nic Barker db04381285 Enable debug mode in multi config example 2025-01-10 09:17:31 +13:00
Harrison Lambeth 944d290428
[Core] Multi instance / context support (#174) 2025-01-10 09:08:48 +13:00
Nic Barker 3f01ee4a4e Disable cairo example because of github actions issues 2025-01-09 10:08:51 +13:00
FintasticMan a431254de4
[Core] Add check for supported C/C++ versions (#144) 2025-01-09 10:05:50 +13:00
David Styrbjörn 7cc719e61f
[Documentation] Updated example for Clay_SetPointerState (#169) 2025-01-09 09:56:24 +13:00
Nic Barker c12cefeaf4
[Layout] Improve shrink size distribution (#173) 2025-01-08 19:39:20 +13:00
Nic Barker fd45553aff [Examples/clay-official-website] Include built website files for official website example 2025-01-06 11:51:05 +13:00
Nic Barker 0d66f57c7e [Examples/clay-official-website] Only play the animation on the landing page when it's on screen 2025-01-06 11:48:44 +13:00
David Styrbjörn 876f38fd20
[Documentation] Updated example for Clay_SetPointerState (#167) 2025-01-06 11:30:25 +13:00
FintasticMan 61cb7c56a7
[Core] Fix default struct initialiser in C++ (#143) 2025-01-06 09:46:21 +13:00
FintasticMan 62077ff0d8
[Core] Fix errors due to cast to same non-trivial type (#155) 2025-01-06 09:26:53 +13:00
Bach Le 4ebe223937
[Core] Fix local id calculation (#50) 2025-01-06 09:19:07 +13:00
vince 6cb9c7c483
fix #99 - [Core] Bug in text wrapping at very narrow widths (#163) 2025-01-05 14:34:36 +13:00
vince 723f59dffd
[Renderers/Web] treat RenderCommand.commandType as uint8_t instead of uint32_t (#162) 2025-01-05 14:34:16 +13:00
Peter Zmanovsky bcb555fd10
Fix possible NULL pointer dereference (#153) 2025-01-04 13:26:58 +13:00
Nic Barker cf12cd6af8
[Core] Standardise number types to int32_t for array indices, lengths and capacities (#152) 2025-01-03 11:24:32 +13:00
Nic Barker a44423a133 Add comment to explain CLAY macro 2025-01-03 10:19:59 +13:00
FintasticMan cd01083ffe
[Core] Simplify CLAY macro (#119) 2025-01-03 10:02:58 +13:00
Nic Barker 68fbb07311
[Bindings/Odin] Update Odin bindings to latest (#151) 2025-01-03 09:59:09 +13:00
Stowy 902ff3b0a9
Fixed compilation using clang on windows (#134) 2024-12-31 17:51:18 +13:00
SuperOpt 2938c00dc8
[Renderers/Cairo] Add FindCairo.cmake (#122) 2024-12-31 13:32:56 +13:00
SuperOpt ba78b35604
C++ projects should use CXX flags (#136) 2024-12-31 13:29:49 +13:00
SuperOpt c9e1a63378
[Compilers] C projects should use C flags rather than CXX flags (#123) 2024-12-30 13:28:24 +13:00
Nic Barker 20543bdc74 Fix a typof of #if and #ifdef" 2024-12-30 13:11:32 +13:00
FintasticMan c13eef1c1e
[Core] Fix more C99 compliance issues (#118) 2024-12-30 13:09:14 +13:00
Junior Rantila c24a41b9e4
Add Clay_IsDebugModeEnabled() (#130) 2024-12-30 12:04:48 +13:00
FintasticMan 5831a8ac7c
[Examples/Intro] Fix NULL pointer deref due to huuge malloc (#120) 2024-12-29 06:39:38 +13:00
Nic Barker 37af99b221 Add missing Clay_PointerOver to header definition 2024-12-28 22:33:41 +13:00
Nic Barker 5fe11c6535 Fix a bug in the HTML renderer 2024-12-28 20:30:38 +13:00
Nic Barker ac473d6fe7
[Renderers/SDL2] Create initial SDL2 renderer (#115)
Co-authored-by: Junior Rantila <junior.rantila@gmail.com>
2024-12-28 19:15:22 +13:00
FintasticMan bec56e68a4
Fix a couple of standards-compliance issues with C99 (#81) 2024-12-27 11:12:59 +13:00
Nic Barker 2c8856a91e Add bindings/rust directory with links to external rust bindings 2024-12-27 11:11:30 +13:00
Nic Barker c0c90250a9 update README 2024-12-27 10:54:09 +13:00
Nic Barker 4cfbdf2a0c Add example from introduction video 2024-12-27 10:52:12 +13:00
Anthony Carbajal 08033b03cb
[Renderers/Raylib] Update files for v5.5 release (#109) 2024-12-26 20:02:33 +13:00
Nic Barker 04694b0da2
[Core] Implement Error Handler / Callback (#105) 2024-12-26 19:58:59 +13:00
Nic Barker 6a9b723dcc Fix an issue where debug tools weren't scrollable 2024-12-24 22:28:01 +13:00
Hayden Kowalchuk 9904ca533a
fix: move internal types to stdint specific. Match Clay_RenderCommandArray_Get protoypes (#78) 2024-12-22 19:22:10 +13:00
Nic Barker a48d40635a remove inline functions for better windows lib compatibility 2024-12-22 09:13:28 +13:00
Nic Barker 06025651ce remove VERSION file that was causing some CMake issues 2024-12-21 08:50:48 +13:00
Nic Barker 712a79c473
Allow floating configuration to capture pointer (#66) 2024-12-21 06:36:34 +13:00
Alexey Mostovoy b2dba60711
Fix variable name in README.md (#59) 2024-12-20 13:00:19 +13:00
Oleksii Bulba 35d72e5fba
Added window dimensions and title to Clay_Raylib_Initialize function (#56) 2024-12-19 21:34:20 +13:00
Nic Barker 18f06e5faf
Implement native scroll containers in HTML renderer (#54) 2024-12-19 11:35:18 +13:00
Oleksii Bulba b9d02330ff
Fix: moved CLAY__MIN and CLAY__MAX to public macros (#55) 2024-12-19 09:29:12 +13:00
Nic Barker ff3b69d94f Fix a bug with borders that could cause duplicate IDs 2024-12-12 10:31:41 +13:00
Nic Barker d2e6be64a9 defer rendering of borders until after children 2024-12-05 13:54:54 +13:00
Nic Barker 0ef877eef7 fix an edge case where text could be measured incorrectly 2024-12-05 13:23:20 +13:00
Nic Barker efec6ab1de Fix a bug that could cause double newlines in text blocks 2024-12-04 16:02:53 +13:00
Nic Barker 9f0cf751da Fix incorrect handling of naked newline characters in text blocks 2024-12-04 15:46:02 +13:00
Nic Barker 41f6616993 Fix text measurement default bucket count 2024-12-02 12:04:01 +13:00
Nic Barker b8725bfb65
Improve overflow handling / CLAY_MAX_ELEMENT_COUNT exceeded (#52) 2024-12-01 21:05:56 +13:00
Nic Barker 51c5355686
Fix text cache overflow (#51) 2024-11-27 12:13:11 +13:00
Justin 330e56a858
Include new cairo renderer (#48) 2024-11-19 15:03:39 +11:00
Bach Le c5a1c1a4ed
Forward declare Clay__OpenTextElement (#49) 2024-11-18 15:01:55 +11:00
Justin f517c00ed0
docs: remove some inconsistencies with current API (#47) 2024-11-15 09:35:02 +11:00
Nic Barker 8b84561f0d Add Clay_GetElementWithIndex function 2024-10-24 21:38:02 +13:00
Nic Barker 2f67b61256 README updates 2024-10-23 09:44:39 +13:00
Nic Barker 2d6e9afe87 Fix GCC type cast 2024-10-22 21:12:47 +13:00
Nic Barker 83551449c2 Fix a bug when a single word couldn't fit in its container 2024-10-22 21:07:24 +13:00
Nic Barker 29ebbb22f0 Fix error in README 2024-10-22 20:52:13 +13:00
Nic Barker 29133bc783
Multi-type elements (#34) 2024-10-22 20:41:35 +13:00
Nic Barker 3dffbea2a3
Fix an overflow bug in the text measurement cache (#44) 2024-10-16 12:11:01 +13:00
Richard Hozák 3b03a79f28
Fix scroll on mouse down being overriden by touch scroll (#42) 2024-10-15 11:47:25 +13:00
johan0A 05eb12bed7
Made casting more explicit for better compatibility with different compilers (#41) 2024-10-12 13:25:22 +13:00
Nic Barker 4ce3105f58 Fix touch scrolling bug on official website 2024-10-12 13:16:25 +13:00
Nic Barker 9a8775751f Cleanup compiler options 2024-10-07 19:30:15 +13:00
Nic Barker f579690f5d Improve space allocation for GROW containers 2024-10-07 18:56:13 +13:00
Nic Barker d826187b50 Update odin bindings 2024-10-06 19:34:42 +13:00
Nic Barker 90b4b0de26 float cast to appease C++ 2024-10-06 13:32:55 +13:00
Nic Barker 843b5bfe89 Add numeric rounding to final bounding box output 2024-10-06 13:29:06 +13:00
Nic Barker 51082d2f1e
Change lineSpacing text config attribute to lineHeight (#37) 2024-10-05 20:57:52 +13:00
Nic Barker 26013e657f Fix a bug that causes incorrect allocation of total width to multiple SIZING_GROW children 2024-10-05 18:55:22 +13:00
Nic Barker c3f2baf40a Fix a bug with scroll offset in scroll containers with GROW where contents were smaller than container 2024-10-05 11:59:29 +13:00
Patrik Smělý c02db35554
[Bug] Fix NEWLINES wrap mode not being respected correctly (#36) 2024-10-04 14:30:49 +13:00
Nic Barker 2cf212e992 Update discord link to permanent and add note for discord link in readme 2024-10-04 10:19:08 +13:00
Nic Barker 761596b36c Add discord link to official website 2024-10-04 10:05:13 +13:00
Patrik Smělý 987e7fde5d
Fix bug in Clay_SetPointerState that causes null pointer deref (#33) 2024-10-02 15:05:52 +13:00
Nic Barker 948b7ce70b
C++20 Support (#31) 2024-09-29 12:53:46 +13:00
Nic Barker 21d9f06a47 Update odin bindings for CLAY_ID_LOCAL 2024-09-28 15:49:43 +12:00
Nic Barker 5e7c4c41eb
[C] Convert element macros to use for() internally (#30) 2024-09-28 15:43:29 +12:00
Nic Barker 1b410dd858 Remove github actions until further notice 2024-09-28 13:03:42 +12:00
Nic Barker f19c72c4e1
Update cmake-multi-platform.yml 2024-09-28 12:58:15 +12:00
Nic Barker 3b6d417d64
Testing out github actions 2024-09-28 12:57:10 +12:00
Nic Barker 3b4c177e58
Fix const inits for gcc 9.4 and add docker tests for old gcc compiler (#29) 2024-09-28 07:42:08 +12:00
Bach Le 10cc866477
Add CLAY_LOCAL_ID[I] (#27) 2024-09-25 14:04:28 +12:00
Nic Barker 3775927e40 Replace const structs with macros to appease GCC 2024-09-25 13:43:23 +12:00
Bach Le c60fb1300f
Initialize text measurement hashmap (#25) 2024-09-25 13:30:36 +12:00
Severin Denisenko 52b3f6a14e
Use shallow clone on Raylib (#23)
This change reduces time of project configuration from 24 second to 12 seconds
2024-09-22 13:27:29 +12:00
Michael Savage 3e0791bdc5
Don't use __VA_ARGS__ in a non-variadic macro (#21) 2024-09-21 08:14:54 +12:00
Nic Barker 6430bbbdc3 Fix warnings caused by misname 2024-09-21 08:13:22 +12:00
Nic Barker 5bb725d005 Fix forward declarations 2024-09-21 08:04:31 +12:00
Nic Barker 0903440a34 Add notice for defining implementation macro at the top of the file 2024-09-20 20:56:33 +12:00
Nic Barker 332837befd
Fix issues with multiple translation units (#20) 2024-09-20 20:46:48 +12:00
Nic Barker b2d922c78d Fix a compile bug in update scroll containers 2024-09-20 07:12:11 +12:00
124 changed files with 20037 additions and 4849 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
cmake-build-debug/
cmake-build-release/
.DS_Store
.idea/
build/
node_modules/

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

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

View file

@ -0,0 +1,92 @@
name: CMake on multiple platforms
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
fail-fast: false
# Set up a matrix to run the following 3 configurations:
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc, clang, cl]
include:
- os: windows-latest
c_compiler: cl
cpp_compiler: cl
- os: ubuntu-latest
c_compiler: gcc
cpp_compiler: g++
- os: ubuntu-latest
c_compiler: clang
cpp_compiler: clang++
exclude:
- os: windows-latest
c_compiler: gcc
- os: windows-latest
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache
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"
# An explicit key for restoring and saving the cache
key: "_deps"
- name: Install Dependencies
if: runner.os == 'Linux'
run: |
DEBIAN_FRONTEND=noninteractive sudo apt-get update -y
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libwayland-dev
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y pkg-config
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libxkbcommon-dev
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y xorg-dev
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }}

View file

@ -0,0 +1,104 @@
name: Odin Bindings Update
on:
push:
branches: [main]
jobs:
check_changes:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check_clay.outputs.changed }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check if clay.h changed
id: check_clay
run: |
if git diff --name-only HEAD^ HEAD | grep -Fx "clay.h"; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
build:
needs: check_changes
if: needs.check_changes.outputs.changed == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install clang (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y clang
- name: Build libs
run: |
mkdir -p build
mkdir -p artifacts
cp clay.h clay.c
COMMON_FLAGS="-DCLAY_IMPLEMENTATION -fno-ident -frandom-seed=clay"
if [[ "$(uname)" == "Linux" ]]; then
mkdir -p artifacts/linux
mkdir -p artifacts/windows
mkdir -p artifacts/wasm
echo "Building for Linux..."
clang -c $COMMON_FLAGS -fPIC -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -o build/linux.o
ar rD artifacts/linux/clay.a build/linux.o
echo "Building for Windows..."
clang -c $COMMON_FLAGS -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib clay.c -o artifacts/windows/clay.lib
echo "Building for WASM..."
clang -c $COMMON_FLAGS -fPIC -target wasm32 -nostdlib -static clay.c -o artifacts/wasm/clay.o
elif [[ "$(uname)" == "Darwin" ]]; then
mkdir -p artifacts/macos
mkdir -p artifacts/macos-arm64
echo "Building for macOS (x86_64)..."
clang -c $COMMON_FLAGS -fPIC -target x86_64-apple-macos clay.c -o build/macos.o
libtool -static -o artifacts/macos/clay.a build/macos.o
echo "Building for macOS (ARM64)..."
clang -c $COMMON_FLAGS -fPIC -target arm64-apple-macos clay.c -o build/macos-arm64.o
libtool -static -o artifacts/macos-arm64/clay.a build/macos-arm64.o
fi
rm -f clay.c build/*.o
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts-${{ matrix.os }}
path: artifacts/
commit:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Move artifacts
run: |
cp -r artifacts-ubuntu-latest/* bindings/odin/clay-odin/
cp -r artifacts-macos-latest/* bindings/odin/clay-odin/
- name: Commit/Push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bindings/odin/clay-odin/
git commit -m "[bindings/odin] Update Odin bindings"
git push

3
.gitignore vendored
View file

@ -2,5 +2,6 @@ cmake-build-debug/
cmake-build-release/ cmake-build-release/
.DS_Store .DS_Store
.idea/ .idea/
build/
node_modules/ node_modules/
*.dSYM
.vs/

View file

@ -1,7 +1,62 @@
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.27)
project(clay C) project(clay)
set(CMAKE_C_STANDARD 99) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_subdirectory("examples/raylib-sidebar-scrolling-container") option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON)
add_subdirectory("examples/clay-official-website") option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF)
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF)
option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF)
option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF)
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
if(APPLE)
enable_language(OBJC)
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE)
add_subdirectory("examples/cpp-project-example")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS)
if(NOT MSVC)
add_subdirectory("examples/clay-official-website")
add_subdirectory("examples/terminal-example")
endif()
add_subdirectory("examples/introducing-clay-video-demo")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES)
add_subdirectory("examples/raylib-multi-context")
add_subdirectory("examples/raylib-sidebar-scrolling-container")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES)
add_subdirectory("examples/SDL2-video-demo")
endif ()
if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES))
add_subdirectory("examples/SDL3-simple-demo")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES)
add_subdirectory("examples/sokol-video-demo")
add_subdirectory("examples/sokol-corner-radius")
endif()
# Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement
if(CLAY_INCLUDE_PLAYDATE_EXAMPLES)
add_subdirectory("examples/playdate-project-example")
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 .)

2505
README.md

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
0.11

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

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

1
bindings/csharp/README Normal file
View file

@ -0,0 +1 @@
https://github.com/Orcolom/clay-cs

View file

@ -1,6 +1,6 @@
### Odin Language Bindings ### 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 Special thanks to
@ -8,21 +8,21 @@ Special thanks to
- [Dudejoe870](https://github.com/Dudejoe870) - [Dudejoe870](https://github.com/Dudejoe870)
- MrStevns from the Odin Discord server - 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. 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):
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
// C form of element macros // 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), { // Define an element with 16px of x and y padding
// Child elements here CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
}); // Child elements here
}
``` ```
```Odin ```Odin
// Odin form of element macros // 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 })) { if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) {
// Child elements here // Child elements here
} }
``` ```
@ -34,111 +34,170 @@ if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BO
import clay "clay-odin" 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 ```Odin
minMemorySize: u32 = clay.MinMemorySize() error_handler :: proc "c" (errorData: clay.ErrorData) {
memory := make([^]u8, minMemorySize) // Do something with the error data.
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory) }
clay.Initialize(arena)
min_memory_size := clay.MinMemorySize()
memory := make([^]u8, min_memory_size)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory)
clay.Initialize(arena, {1080, 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 ```Odin
// Example measure text function // Example measure text function
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions { measure_text :: proc "c" (
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc 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 // 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 // Tell clay how to measure text
clay.SetMeasureTextFunction(measureText) clay.SetMeasureTextFunction(measure_text, nil)
``` ```
4. **Optional** - Call [clay.SetPointerPosition(pointerPosition)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerposition) if you want to use mouse interactions. 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 ```Odin
// Update internal pointer position for handling mouseover / click / touch events // Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY }) clay.SetPointerState(
{ 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 ```Odin
// Define some colors.
COLOR_LIGHT :: clay.Color{224, 215, 210, 255} COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255} COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 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 // Layout config is just a struct that can be declared statically, or inline
sidebarItemLayout := clay.LayoutConfig { sidebar_item_layout := clay.LayoutConfig {
sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)}, sizing = {
width = clay.SizingGrow({}),
height = clay.SizingFixed(50)
},
} }
// Re-useable components are just normal functions // Re-useable components are just normal procs.
SidebarItemComponent :: proc(index: u32) { sidebar_item_component :: proc(index: u32) {
if clay.Rectangle(clay.ID("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {} 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 // An example function to create your layout tree
CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) { create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
clay.BeginLayout(windowWidth, windowHeight) // Begin constructing the layout.
clay.BeginLayout()
// An example of laying out a UI with a fixed width sidebar and flexible width main content // 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 // NOTE: To create a scope for child components, the Odin API uses `if` with components that have children
if clay.Rectangle( if clay.UI()({
clay.ID("OuterContainer"), id = clay.ID("OuterContainer"),
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}), layout = {
clay.RectangleConfig({color = {250, 250, 255, 255}}), sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
) { padding = { 16, 16, 16, 16 },
if clay.Rectangle( childGap = 16,
clay.ID("SideBar"), },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingFixed(300), height = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}), backgroundColor = { 250, 250, 255, 255 },
clay.RectangleConfig({color = COLOR_LIGHT}), }) {
) { if clay.UI()({
if clay.Rectangle( id = clay.ID("SideBar"),
clay.ID("ProfilePictureOuter"), layout = {
clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}), layoutDirection = .TopToBottom,
clay.RectangleConfig({color = COLOR_RED}), sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) },
) { padding = { 16, 16, 16, 16 },
if clay.Image( childGap = 16,
clay.ID("ProfilePicture"), },
clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}), backgroundColor = COLOR_LIGHT,
clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}), }) {
) {} if clay.UI()({
clay.Text(clay.ID("ProfileTitle"), "Clay - UI Library", clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}})) 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 = {
// How you define `profile_picture` depends on your renderer.
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 // Standard Odin code like loops, etc. work inside components.
for i in 0..<10 { // Here we render 5 sidebar items.
SidebarItemComponent(i) for i in u32(0)..<5 {
sidebar_item_component(i)
} }
} }
if clay.Rectangle( if clay.UI()({
clay.ID("MainContent"), id = clay.ID("MainContent"),
clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}), layout = {
clay.RectangleConfig({color = COLOR_LIGHT}), sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
) {} },
backgroundColor = COLOR_LIGHT,
}) {}
} }
// ...
// Returns a list of render commands
return clay.EndLayout()
} }
``` ```
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 ```Odin
renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight) render_commands := create_layout()
for i: u32 = 0; i < renderCommands.length; i += 1 { for i in 0..<i32(render_commands.length) {
renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i) render_command := clay.RenderCommandArray_Get(render_commands, i)
switch renderCommand.commandType { switch render_command.commandType {
case .Rectangle: case .Rectangle:
DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color) DrawRectangle(render_command.boundingBox, render_command.config.rectangleElementConfig.color)
// ... Implement handling of other command types // ... 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; cp ../../clay.h clay.c;
# Intel Mac # Intel Mac
clang -c -o clay.o -static -target x86_64-apple-darwin clay.c -fPIC && ar r clay-odin/macos/clay.a clay.o; rm -f clay-odin/macos/clay.a && 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 # ARM Mac
clang -c -o clay.o -static clay.c -fPIC && ar r clay-odin/macos-arm64/clay.a clay.o; rm -f clay-odin/macos-arm64/clay.a && 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 # x64 Windows
clang -c -o clay-odin/windows/clay.lib -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static clay.c; rm -f clay-odin/windows/clay.lib && 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 # Linux
clang -c -o clay.o -static -target x86_64-unknown-linux-gnu clay.c -fPIC && ar r clay-odin/linux/clay.a clay.o; rm -f clay-odin/linux/clay.a && 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 # WASM
clang -c -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static clay.c; rm -f clay-odin/wasm/clay.o && clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c;
rm clay.o; rm clay.o;
rm clay.c; rm clay.c;

View file

@ -1,433 +1,498 @@
package clay package clay
import "core:c" import "core:c"
import "core:strings"
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
foreign import Clay "windows/clay.lib" foreign import Clay "windows/clay.lib"
} else when ODIN_OS == .Linux { } else when ODIN_OS == .Linux {
foreign import Clay "linux/clay.a" foreign import Clay "linux/clay.a"
} else when ODIN_OS == .Darwin { } else when ODIN_OS == .Darwin {
when ODIN_ARCH == .arm64 { when ODIN_ARCH == .arm64 {
foreign import Clay "macos-arm64/clay.a" foreign import Clay "macos-arm64/clay.a"
} else { } else {
foreign import Clay "macos/clay.a" foreign import Clay "macos/clay.a"
} }
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import Clay "wasm/clay.o" foreign import Clay "wasm/clay.o"
} }
String :: struct { String :: struct {
length: c.int, isStaticallyAllocated: c.bool,
chars: [^]c.char, length: c.int32_t,
chars: [^]c.char,
}
StringSlice :: struct {
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
} }
Vector2 :: [2]c.float Vector2 :: [2]c.float
Dimensions :: struct { Dimensions :: struct {
width: c.float, width: c.float,
height: c.float, height: c.float,
} }
Arena :: struct { Arena :: struct {
label: String, nextAllocation: uintptr,
nextAllocation: u64, capacity: c.size_t,
capacity: u64, memory: [^]c.char,
memory: [^]c.char,
} }
BoundingBox :: struct { BoundingBox :: struct {
x: c.float, x: c.float,
y: c.float, y: c.float,
width: c.float, width: c.float,
height: c.float, height: c.float,
} }
Color :: [4]c.float Color :: [4]c.float
CornerRadius :: struct { CornerRadius :: struct {
topLeft: c.float, topLeft: c.float,
topRight: c.float, topRight: c.float,
bottomLeft: c.float, bottomLeft: c.float,
bottomRight: c.float, bottomRight: c.float,
} }
BorderData :: struct { BorderData :: struct {
width: u32, width: u32,
color: Color, color: Color,
} }
ElementId :: struct { ElementId :: struct {
id: u32, id: u32,
offset: u32, offset: u32,
baseId: u32, baseId: u32,
stringId: String, stringId: String,
} }
when ODIN_OS == .Windows { when ODIN_OS == .Windows {
EnumBackingType :: u32 EnumBackingType :: u32
} else { } else {
EnumBackingType :: u8 EnumBackingType :: u8
} }
RenderCommandType :: enum EnumBackingType { RenderCommandType :: enum EnumBackingType {
None, None,
Rectangle, Rectangle,
Border, Border,
Text, Text,
Image, Image,
ScissorStart, ScissorStart,
ScissorEnd, ScissorEnd,
Custom, Custom,
} }
RectangleElementConfig :: struct { RectangleElementConfig :: struct {
color: Color, color: Color,
cornerRadius: CornerRadius,
} }
TextWrapMode :: enum EnumBackingType { TextWrapMode :: enum EnumBackingType {
Words, Words,
Newlines, Newlines,
None, None,
}
TextAlignment :: enum EnumBackingType {
Left,
Center,
Right,
} }
TextElementConfig :: struct { TextElementConfig :: struct {
textColor: Color, userData: rawptr,
fontId: u16, textColor: Color,
fontSize: u16, fontId: u16,
letterSpacing: u16, fontSize: u16,
lineSpacing: u16, letterSpacing: u16,
wrapMode: TextWrapMode, lineHeight: u16,
wrapMode: TextWrapMode,
textAlignment: TextAlignment,
}
AspectRatioElementConfig :: struct {
aspectRatio: f32,
} }
ImageElementConfig :: struct { ImageElementConfig :: struct {
imageData: rawptr, imageData: rawptr,
sourceDimensions: Dimensions,
} }
CustomElementConfig :: struct { CustomElementConfig :: struct {
customData: rawptr, customData: rawptr,
}
BorderWidth :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
betweenChildren: u16,
} }
BorderElementConfig :: struct { BorderElementConfig :: struct {
left: BorderData, color: Color,
right: BorderData, width: BorderWidth,
top: BorderData,
bottom: BorderData,
betweenChildren: BorderData,
cornerRadius: CornerRadius,
} }
ScrollElementConfig :: struct { ClipElementConfig :: struct {
horizontal: bool, horizontal: bool, // clip overflowing elements on the "X" axis
vertical: bool, vertical: bool, // clip overflowing elements on the "Y" axis
childOffset: Vector2, // offsets the [X,Y] positions of all child elements, primarily for scrolling containers
} }
FloatingAttachPointType :: enum EnumBackingType { FloatingAttachPointType :: enum EnumBackingType {
LEFT_TOP, LeftTop,
LEFT_CENTER, LeftCenter,
LEFT_BOTTOM, LeftBottom,
CENTER_TOP, CenterTop,
CENTER_CENTER, CenterCenter,
CENTER_BOTTOM, CenterBottom,
RIGHT_TOP, RightTop,
RIGHT_CENTER, RightCenter,
RIGHT_BOTTOM, RightBottom,
} }
FloatingAttachPoints :: struct { FloatingAttachPoints :: struct {
element: FloatingAttachPointType, element: FloatingAttachPointType,
parent: FloatingAttachPointType, parent: FloatingAttachPointType,
}
PointerCaptureMode :: enum EnumBackingType {
Capture,
Passthrough,
}
FloatingAttachToElement :: enum EnumBackingType {
None,
Parent,
ElementWithId,
Root,
}
FloatingClipToElement :: enum EnumBackingType {
None,
AttachedParent,
} }
FloatingElementConfig :: struct { FloatingElementConfig :: struct {
offset: Vector2, offset: Vector2,
expand: Dimensions, expand: Dimensions,
zIndex: u16, parentId: u32,
parentId: u32, zIndex: i16,
attachment: FloatingAttachPoints, attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
clipTo: FloatingClipToElement,
} }
ElementConfigUnion :: struct #raw_union { TextRenderData :: struct {
rectangleElementConfig: ^RectangleElementConfig, stringContents: StringSlice,
textElementConfig: ^TextElementConfig, textColor: Color,
imageElementConfig: ^ImageElementConfig, fontId: u16,
customElementConfig: ^CustomElementConfig, fontSize: u16,
borderElementConfig: ^BorderElementConfig, letterSpacing: u16,
lineHeight: u16,
}
RectangleRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
}
ImageRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
imageData: rawptr,
}
CustomRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
customData: rawptr,
}
BorderRenderData :: struct {
color: Color,
cornerRadius: CornerRadius,
width: BorderWidth,
}
RenderCommandData :: struct #raw_union {
rectangle: RectangleRenderData,
text: TextRenderData,
image: ImageRenderData,
custom: CustomRenderData,
border: BorderRenderData,
} }
RenderCommand :: struct { RenderCommand :: struct {
boundingBox: BoundingBox, boundingBox: BoundingBox,
config: ElementConfigUnion, renderData: RenderCommandData,
text: String, userData: rawptr,
id: u32, id: u32,
commandType: RenderCommandType, zIndex: i16,
commandType: RenderCommandType,
} }
ScrollContainerData :: struct { ScrollContainerData :: struct {
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. // 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. // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
scrollPosition: ^Vector2, scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions, scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions, contentDimensions: Dimensions,
config: ScrollElementConfig, config: ClipElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool, found: bool,
}
ElementData :: struct {
boundingBox: BoundingBox,
found: bool,
}
PointerDataInteractionState :: enum EnumBackingType {
PressedThisFrame,
Pressed,
ReleasedThisFrame,
Released,
}
PointerData :: struct {
position: Vector2,
state: PointerDataInteractionState,
} }
SizingType :: enum EnumBackingType { SizingType :: enum EnumBackingType {
FIT, Fit,
GROW, Grow,
PERCENT, Percent,
Fixed,
} }
SizingConstraintsMinMax :: struct { SizingConstraintsMinMax :: struct {
min: c.float, min: c.float,
max: c.float, max: c.float,
} }
SizingConstraints :: struct #raw_union { SizingConstraints :: struct #raw_union {
sizeMinMax: SizingConstraintsMinMax, sizeMinMax: SizingConstraintsMinMax,
sizePercent: c.float, sizePercent: c.float,
} }
SizingAxis :: struct { SizingAxis :: struct {
// Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions // Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions
constraints: SizingConstraints, constraints: SizingConstraints,
type: SizingType, type: SizingType,
} }
Sizing :: struct { Sizing :: struct {
width: SizingAxis, width: SizingAxis,
height: SizingAxis, height: SizingAxis,
} }
Padding :: struct { Padding :: struct {
x: u16, left: u16,
y: u16, right: u16,
top: u16,
bottom: u16,
} }
LayoutDirection :: enum EnumBackingType { LayoutDirection :: enum EnumBackingType {
LEFT_TO_RIGHT, LeftToRight,
TOP_TO_BOTTOM, TopToBottom,
} }
LayoutAlignmentX :: enum EnumBackingType { LayoutAlignmentX :: enum EnumBackingType {
LEFT, Left,
RIGHT, Right,
CENTER, Center,
} }
LayoutAlignmentY :: enum EnumBackingType { LayoutAlignmentY :: enum EnumBackingType {
TOP, Top,
BOTTOM, Bottom,
CENTER, Center,
} }
ChildAlignment :: struct { ChildAlignment :: struct {
x: LayoutAlignmentX, x: LayoutAlignmentX,
y: LayoutAlignmentY, y: LayoutAlignmentY,
} }
LayoutConfig :: struct { LayoutConfig :: struct {
sizing: Sizing, sizing: Sizing,
padding: Padding, padding: Padding,
childGap: u16, childGap: u16,
layoutDirection: LayoutDirection, childAlignment: ChildAlignment,
childAlignment: ChildAlignment, layoutDirection: LayoutDirection,
} }
ClayArray :: struct($type: typeid) { ClayArray :: struct($type: typeid) {
capacity: u32, capacity: i32,
length: u32, length: i32,
internalArray: [^]type, internalArray: [^]type,
} }
ElementDeclaration :: struct {
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
aspectRatio: AspectRatioElementConfig,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
clip: ClipElementConfig,
border: BorderElementConfig,
userData: rawptr,
}
ErrorType :: enum EnumBackingType {
TextMeasurementFunctionNotProvided,
ArenaCapacityExceeded,
ElementsCapacityExceeded,
TextMeasurementCapacityExceeded,
DuplicateId,
FloatingContainerParentNotFound,
PercentageOver1,
InternalError,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr,
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr,
}
Context :: struct {} // opaque structure, only use as a pointer
@(link_prefix = "Clay_", default_calling_convention = "c") @(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay { foreign Clay {
MinMemorySize :: proc() -> u32 --- _OpenElement :: proc() ---
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena --- _OpenElementWithId :: proc(id: ElementId) ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) --- _CloseElement :: proc() ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions) --- MinMemorySize :: proc() -> u32 ---
UpdateScrollContainers :: proc(isPointerActive: bool, scrollDelta: Vector2, deltaTime: c.float) --- CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
SetLayoutDimensions :: proc(dimensions: Dimensions) --- SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
BeginLayout :: proc() --- Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context ---
EndLayout :: proc() -> ClayArray(RenderCommand) --- GetCurrentContext :: proc() -> ^Context ---
PointerOver :: proc(id: ElementId) -> bool --- SetCurrentContext :: proc(ctx: ^Context) ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData --- UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: ^String, config: ^TextElementConfig) -> Dimensions) --- SetLayoutDimensions :: proc(dimensions: Dimensions) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand --- BeginLayout :: proc() ---
SetDebugModeEnabled :: proc(enabled: bool) --- EndLayout :: proc() -> ClayArray(RenderCommand) ---
} GetElementId :: proc(id: String) -> ElementId ---
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
@(private, link_prefix = "Clay_", default_calling_convention = "c") GetElementData :: proc(id: ElementId) -> ElementData ---
foreign _ { Hovered :: proc() -> bool ---
_layoutConfigs: ClayArray(LayoutConfig) OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
_rectangleElementConfigs: ClayArray(RectangleElementConfig) PointerOver :: proc(id: ElementId) -> bool ---
_textElementConfigs: ClayArray(TextElementConfig) GetScrollOffset :: proc() -> Vector2 ---
_imageElementConfigs: ClayArray(ImageElementConfig) GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
_floatingElementConfigs: ClayArray(FloatingElementConfig) SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
_customElementConfigs: ClayArray(CustomElementConfig) SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
_scrollElementConfigs: ClayArray(ScrollElementConfig) RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
_borderElementConfigs: ClayArray(BorderElementConfig) 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) @(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay { foreign Clay {
_OpenContainerElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig) --- _ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_OpenRectangleElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, rectangleConfig: ^RectangleElementConfig) --- _HashString :: proc(key: String, seed: u32) -> ElementId ---
_OpenTextElement :: proc(id: ElementId, text: String, textConfig: ^TextElementConfig) --- _HashStringWithOffset :: proc(key: String, index: u32, seed: u32) -> ElementId ---
_OpenImageElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^ImageElementConfig) --- _OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_OpenScrollElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^ScrollElementConfig) --- _StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
_OpenFloatingElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^FloatingElementConfig) --- _GetParentElementId :: proc() -> u32 ---
_OpenBorderElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^BorderElementConfig) ---
_OpenCustomElement :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^CustomElementConfig) ---
_CloseElementWithChildren :: proc() ---
_CloseScrollElement :: proc() ---
_CloseFloatingElement :: proc() ---
_LayoutConfigArray_Add :: proc(array: ^ClayArray(LayoutConfig), config: LayoutConfig) -> ^LayoutConfig ---
_RectangleElementConfigArray_Add :: proc(array: ^ClayArray(RectangleElementConfig), config: RectangleElementConfig) -> ^RectangleElementConfig ---
_TextElementConfigArray_Add :: proc(array: ^ClayArray(TextElementConfig), config: TextElementConfig) -> ^TextElementConfig ---
_ImageElementConfigArray_Add :: proc(array: ^ClayArray(ImageElementConfig), config: ImageElementConfig) -> ^ImageElementConfig ---
_FloatingElementConfigArray_Add :: proc(array: ^ClayArray(FloatingElementConfig), config: FloatingElementConfig) -> ^FloatingElementConfig ---
_CustomElementConfigArray_Add :: proc(array: ^ClayArray(CustomElementConfig), config: CustomElementConfig) -> ^CustomElementConfig ---
_ScrollElementConfigArray_Add :: proc(array: ^ClayArray(ScrollElementConfig), config: ScrollElementConfig) -> ^ScrollElementConfig ---
_BorderElementConfigArray_Add :: proc(array: ^ClayArray(BorderElementConfig), config: BorderElementConfig) -> ^BorderElementConfig ---
_HashString :: proc(toHash: String, index: u32) -> ElementId ---
} }
@(require_results, deferred_none = _CloseElementWithChildren) ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool {
Container :: proc(id: ElementId, layoutConfig: ^LayoutConfig) -> bool { _ConfigureOpenElement(config)
_OpenContainerElement(id, layoutConfig) return true
return true
} }
@(require_results, deferred_none = _CloseElementWithChildren) @(deferred_none = _CloseElement)
Rectangle :: proc(id: ElementId, layoutConfig: ^LayoutConfig, rectangleConfig: ^RectangleElementConfig) -> bool { UI_WithId :: proc(id: ElementId) -> proc (config: ElementDeclaration) -> bool {
_OpenRectangleElement(id, layoutConfig, rectangleConfig) _OpenElementWithId(id)
return true return ConfigureOpenElement
} }
Text :: proc(id: ElementId, text: string, textConfig: ^TextElementConfig) -> bool { @(deferred_none = _CloseElement)
_OpenTextElement(id, MakeString(text), textConfig) UI_AutoId :: proc() -> proc (config: ElementDeclaration) -> bool {
return true _OpenElement()
return ConfigureOpenElement
} }
@(require_results, deferred_none = _CloseElementWithChildren) UI :: proc{UI_WithId, UI_AutoId};
Image :: proc(id: ElementId, layoutConfig: ^LayoutConfig, imageConfig: ^ImageElementConfig) -> bool {
_OpenImageElement(id, layoutConfig, imageConfig) Text :: proc($text: string, config: ^TextElementConfig) {
return true wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
} }
@(require_results, deferred_none = _CloseScrollElement) TextDynamic :: proc(text: string, config: ^TextElementConfig) {
Scroll :: proc(id: ElementId, layoutConfig: ^LayoutConfig, scrollConfig: ^ScrollElementConfig) -> bool { _OpenTextElement(MakeString(text), config)
_OpenScrollElement(id, layoutConfig, scrollConfig)
return true
}
@(require_results, deferred_none = _CloseFloatingElement)
Floating :: proc(id: ElementId, layoutConfig: ^LayoutConfig, floatingConfig: ^FloatingElementConfig) -> bool {
_OpenFloatingElement(id, layoutConfig, floatingConfig)
return true
}
@(require_results, deferred_none = _CloseElementWithChildren)
Border :: proc(id: ElementId, layoutConfig: ^LayoutConfig, borderConfig: ^BorderElementConfig) -> bool {
_OpenBorderElement(id, layoutConfig, borderConfig)
return true
}
@(require_results, deferred_none = _CloseElementWithChildren)
Custom :: proc(id: ElementId, layoutConfig: ^LayoutConfig, customConfig: ^CustomElementConfig) -> bool {
_OpenCustomElement(id, layoutConfig, customConfig)
return true
}
Layout :: proc(config: LayoutConfig) -> ^LayoutConfig {
return _LayoutConfigArray_Add(&_layoutConfigs, config)
}
RectangleConfig :: proc(config: RectangleElementConfig) -> ^RectangleElementConfig {
return _RectangleElementConfigArray_Add(&_rectangleElementConfigs, config)
} }
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig { TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
return _TextElementConfigArray_Add(&_textElementConfigs, config) return _StoreTextElementConfig(config)
} }
ImageConfig :: proc(config: ImageElementConfig) -> ^ImageElementConfig { PaddingAll :: proc(allPadding: u16) -> Padding {
return _ImageElementConfigArray_Add(&_imageElementConfigs, config) return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
} }
FloatingConfig :: proc(config: FloatingElementConfig) -> ^FloatingElementConfig { BorderOutside :: proc(width: u16) -> BorderWidth {
return _FloatingElementConfigArray_Add(&_floatingElementConfigs, config) return {width, width, width, width, 0}
} }
Custom_elementConfig :: proc(config: CustomElementConfig) -> ^CustomElementConfig { BorderAll :: proc(width: u16) -> BorderWidth {
return _CustomElementConfigArray_Add(&_customElementConfigs, config) return {width, width, width, width, width}
}
ScrollConfig :: proc(config: ScrollElementConfig) -> ^ScrollElementConfig {
return _ScrollElementConfigArray_Add(&_scrollElementConfigs, config)
}
BorderConfig :: proc(config: BorderElementConfig) -> ^BorderElementConfig {
return _BorderElementConfigArray_Add(&_borderElementConfigs, config)
}
BorderConfigOutside :: proc(outsideBorders: BorderData) -> ^BorderElementConfig {
return _BorderElementConfigArray_Add(
&_borderElementConfigs,
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders},
)
}
BorderConfigOutsideRadius :: proc(outsideBorders: BorderData, radius: f32) -> ^BorderElementConfig {
return _BorderElementConfigArray_Add(
&_borderElementConfigs,
(BorderElementConfig){left = outsideBorders, right = outsideBorders, top = outsideBorders, bottom = outsideBorders, cornerRadius = {radius, radius, radius, radius}},
)
}
BorderConfigAll :: proc(allBorders: BorderData) -> ^BorderElementConfig {
return _BorderElementConfigArray_Add(
&_borderElementConfigs,
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, betweenChildren = allBorders},
)
}
BorderConfigAllRadius :: proc(allBorders: BorderData, radius: f32) -> ^BorderElementConfig {
return _BorderElementConfigArray_Add(
&_borderElementConfigs,
(BorderElementConfig){left = allBorders, right = allBorders, top = allBorders, bottom = allBorders, cornerRadius = {radius, radius, radius, radius}},
)
} }
CornerRadiusAll :: proc(radius: f32) -> CornerRadius { CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius} return CornerRadius{radius, radius, radius, radius}
} }
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { 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 { 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 { SizingFixed :: proc(size: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.FIT, constraints = {sizeMinMax = {size, size}}} return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
} }
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis { 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 { 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 { ID :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index) return _HashString(MakeString(label), index)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashStringWithOffset(MakeString(label), index, _GetParentElementId())
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -57,48 +57,48 @@ headerTextConfig := clay.TextElementConfig {
textColor = {61, 26, 5, 255}, textColor = {61, 26, 5, 255},
} }
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) { border2pxRed := clay.BorderElementConfig {
if clay.Border( width = { 2, 2, 2, 2, 0 },
clay.ID("HeroBlob", index), color = COLOR_RED
clay.Layout({sizing = {width = clay.SizingGrow({max = 480})}, padding = clay.Padding{16, 16}, childGap = 16, childAlignment = clay.ChildAlignment{y = .CENTER}}), }
clay.BorderConfigOutsideRadius({2, color}, 10),
) { LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.Image( if clay.UI(clay.ID("HeroBlob", index))({
clay.ID("CheckImage", index), layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
clay.Layout({sizing = {width = clay.SizingFixed(32)}}), border = border2pxRed,
clay.ImageConfig({imageData = image, sourceDimensions = {128, 128}}), cornerRadius = clay.CornerRadiusAll(10)
) {} }) {
clay.Text(clay.ID("HeroBlobText", index), text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color})) if clay.UI(clay.ID("CheckImage", index))({
layout = { sizing = { width = clay.SizingFixed(32) } },
aspectRatio = { 1.0 },
image = { imageData = image },
}) {}
clay.Text(text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color}))
} }
} }
LandingPageDesktop :: proc() { LandingPageDesktop :: proc() {
if clay.Container( if clay.UI(clay.ID("LandingPage1Desktop"))({
clay.ID("LandingPage1Desktop"), layout = { sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFit({min = cast(f32)windowHeight - 70})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), }) {
) { if clay.UI(clay.ID("LandingPage1"))({
if clay.Border( layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
clay.ID("LandingPage1"), border = { COLOR_RED, { left = 2, right = 2 } },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), }) {
clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}), if clay.UI(clay.ID("LeftText"))({ layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
) {
if clay.Container(clay.ID("LeftText"), clay.Layout({sizing = {width = clay.SizingPercent(0.55)}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text( clay.Text(
clay.ID("LeftTextTitle"),
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.", "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}), clay.TextConfig({fontSize = 56, fontId = FONT_ID_TITLE_56, textColor = COLOR_RED}),
) )
if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {} if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
clay.Text( clay.Text(
clay.ID("LeftTextTagline"),
"Clay is laying out this webpage right now!", "Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}), clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}),
) )
} }
if clay.Container( if clay.UI(clay.ID("HeroImageOuter"))({
clay.ID("HeroImageOuter"), layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingPercent(0.45) }, childAlignment = { x = .Center }, childGap = 16 },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingPercent(0.45)}, childAlignment = {x = .CENTER}, childGap = 16}), }) {
) {
LandingPageBlob(1, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_5, "High performance", &checkImage5) LandingPageBlob(1, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_5, "High performance", &checkImage5)
LandingPageBlob(2, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4) LandingPageBlob(2, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4)
LandingPageBlob(3, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3) LandingPageBlob(3, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3)
@ -110,35 +110,29 @@ LandingPageDesktop :: proc() {
} }
LandingPageMobile :: proc() { LandingPageMobile :: proc() {
if clay.Container( if clay.UI(clay.ID("LandingPage1Mobile"))({
clay.ID("LandingPage1Mobile"), layout = {
clay.Layout( layoutDirection = .TopToBottom,
{ sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) },
layoutDirection = .TOP_TO_BOTTOM, childAlignment = { x = .Center, y = .Center },
sizing = {width = clay.SizingGrow({}), height = clay.SizingFit({min = cast(f32)windowHeight - 70})}, padding = { 16, 16, 32, 32 },
childAlignment = {x = .CENTER, y = .CENTER}, childGap = 32,
padding = {16, 32}, },
childGap = 32, }) {
}, if clay.UI(clay.ID("LeftText"))({ layout = { sizing = { width = clay.SizingGrow() }, layoutDirection = .TopToBottom, childGap = 8 } }) {
),
) {
if clay.Container(clay.ID("LeftText"), clay.Layout({sizing = {width = clay.SizingGrow({})}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text( clay.Text(
clay.ID("LeftTextTitle"),
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.", "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}), clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}),
) )
if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {} if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
clay.Text( clay.Text(
clay.ID("LeftTextTagline"),
"Clay is laying out this webpage right now!", "Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}), clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}),
) )
} }
if clay.Container( if clay.UI(clay.ID("HeroImageOuter"))({
clay.ID("HeroImageOuter"), layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() }, childAlignment = { x = .Center }, childGap = 16 },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}, childAlignment = {x = .CENTER}, childGap = 16}), }) {
) {
LandingPageBlob(1, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_5, "High performance", &checkImage5) LandingPageBlob(1, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_5, "High performance", &checkImage5)
LandingPageBlob(2, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4) LandingPageBlob(2, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4)
LandingPageBlob(3, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3) LandingPageBlob(3, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3)
@ -150,105 +144,93 @@ LandingPageMobile :: proc() {
FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) { FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED}) textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED})
if clay.Container( if clay.UI(clay.ID("HFileBoxOuter"))({
clay.ID("HFileBoxOuter"), layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = widthSizing}, childAlignment = {y = .CENTER}, padding = {outerPadding, 32}, childGap = 8}), }) {
) { if clay.UI(clay.ID("HFileIncludeOuter"))({ layout = { padding = { 8, 8, 4, 4 } }, backgroundColor = COLOR_RED, cornerRadius = clay.CornerRadiusAll(8) }) {
if clay.Rectangle(clay.ID("HFileIncludeOuter"), clay.Layout({padding = {8, 4}}), clay.RectangleConfig({color = 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(clay.ID("HFileBoxText", 2), "#include clay.h", clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_LIGHT}))
} }
clay.Text(clay.ID("HFileSecondLine"), "~2000 lines of C99.", textConfig) clay.Text("~2000 lines of C99.", textConfig)
clay.Text(clay.ID("HFileBoxText", 5), "Zero dependencies, including no C standard library.", textConfig) clay.Text("Zero dependencies, including no C standard library.", textConfig)
} }
if clay.Container( if clay.UI(clay.ID("BringYourOwnRendererOuter"))({
clay.ID("BringYourOwnRendererOuter"), layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = widthSizing}, childAlignment = {y = .CENTER}, padding = {x = outerPadding, y = 32}, childGap = 8}), }) {
) { clay.Text("Renderer agnostic.", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = COLOR_ORANGE}))
clay.Text(clay.ID("ZeroDependenciesText", 1), "Renderer agnostic.", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = COLOR_ORANGE})) clay.Text("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML.", textConfig)
clay.Text(clay.ID("ZeroDependenciesText", 2), "Layout with clay, then render with Raylib, WebGL Canvas or even as HTML.", textConfig) clay.Text("Flexible output for easy compositing in your custom engine or environment.", textConfig)
clay.Text(clay.ID("ZeroDependenciesText", 3), "Flexible output for easy compositing in your custom engine or environment.", textConfig)
} }
} }
FeatureBlocksDesktop :: proc() { FeatureBlocksDesktop :: proc() {
if clay.Container(clay.ID("FeatureBlocksOuter"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) { if clay.UI(clay.ID("FeatureBlocksOuter"))({ layout = { sizing = { width = clay.SizingGrow({}) } } }) {
if clay.Border( if clay.UI(clay.ID("FeatureBlocksInner"))({
clay.ID("FeatureBlocksInner"), layout = { sizing = { width = clay.SizingGrow() }, childAlignment = { y = .Center } },
clay.Layout({sizing = {width = clay.SizingGrow({})}, childAlignment = {y = .CENTER}}), border = { width = { betweenChildren = 2}, color = COLOR_RED },
clay.BorderConfig({betweenChildren = {width = 2, color = COLOR_RED}}), }) {
) {
FeatureBlocks(clay.SizingPercent(0.5), 50) FeatureBlocks(clay.SizingPercent(0.5), 50)
} }
} }
} }
FeatureBlocksMobile :: proc() { FeatureBlocksMobile :: proc() {
if clay.Border( if clay.UI(clay.ID("FeatureBlocksInner"))({
clay.ID("FeatureBlocksInner"), layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() } },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}}), border = { width = { betweenChildren = 2}, color = COLOR_RED },
clay.BorderConfig({betweenChildren = {width = 2, color = COLOR_RED}}), }) {
) {
FeatureBlocks(clay.SizingGrow({}), 16) FeatureBlocks(clay.SizingGrow({}), 16)
} }
} }
DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.Container(clay.ID("SyntaxPageLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { if clay.UI(clay.ID("SyntaxPageLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(clay.ID("SyntaxPageTextTitle"), "Declarative Syntax", clay.TextConfig(titleTextConfig)) clay.Text("Declarative Syntax", clay.TextConfig(titleTextConfig))
if clay.Container(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} if clay.UI(clay.ID("SyntaxSpacer"))({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
clay.Text( clay.Text(
clay.ID("SyntaxPageTextSubTitle1"),
"Flexible and readable declarative syntax with nested UI element hierarchies.", "Flexible and readable declarative syntax with nested UI element hierarchies.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
) )
clay.Text( clay.Text(
clay.ID("SyntaxPageTextSubTitle2"),
"Mix elements with standard C code like loops, conditionals and functions.", "Mix elements with standard C code like loops, conditionals and functions.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
) )
clay.Text( clay.Text(
clay.ID("SyntaxPageTextSubTitle3"),
"Create your own library of re-usable components from UI primitives like text, images and rectangles.", "Create your own library of re-usable components from UI primitives like text, images and rectangles.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
) )
} }
if clay.Container(clay.ID("SyntaxPageRightImage"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) { if clay.UI(clay.ID("SyntaxPageRightImage"))({ layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.Image( if clay.UI(clay.ID("SyntaxPageRightImageInner"))({
clay.ID("SyntaxPageRightImage"), layout = { sizing = { width = clay.SizingGrow({ max = 568 }) } },
clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}), aspectRatio = { 1136.0 / 1194.0 },
clay.ImageConfig({imageData = &syntaxImage, sourceDimensions = {1136, 1194}}), image = { imageData = &syntaxImage },
) {} }) {}
} }
} }
DeclarativeSyntaxPageDesktop :: proc() { DeclarativeSyntaxPageDesktop :: proc() {
if clay.Container( if clay.UI(clay.ID("SyntaxPageDesktop"))({
clay.ID("SyntaxPageDesktop"), layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), }) {
) { if clay.UI(clay.ID("SyntaxPage"))({
if clay.Border( layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
clay.ID("SyntaxPage"), border = border2pxRed,
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), }) {
clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}),
) {
DeclarativeSyntaxPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5)) DeclarativeSyntaxPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
} }
} }
} }
DeclarativeSyntaxPageMobile :: proc() { DeclarativeSyntaxPageMobile :: proc() {
if clay.Container( if clay.UI(clay.ID("SyntaxPageMobile"))({
clay.ID("SyntaxPageMobile"), layout = {
clay.Layout( layoutDirection = .TopToBottom,
{ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
layoutDirection = .TOP_TO_BOTTOM, childAlignment = { x = .Center, y = .Center },
sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, padding = { 16, 16, 32, 32 },
childAlignment = {x = .CENTER, y = .CENTER}, childGap = 16,
padding = {16, 32}, },
childGap = 16, }) {
},
),
) {
DeclarativeSyntaxPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({})) DeclarativeSyntaxPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({}))
} }
} }
@ -257,163 +239,142 @@ 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} 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) { HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.Container(clay.ID("PerformanceLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { if clay.UI(clay.ID("PerformanceLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(clay.ID("PerformanceTextTitle"), "High Performance", clay.TextConfig(titleTextConfig)) clay.Text("High Performance", clay.TextConfig(titleTextConfig))
if clay.Container(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } }}) {}
clay.Text( clay.Text(
clay.ID("PerformanceTextSubTitle", 1),
"Fast enough to recompute your entire UI every frame.", "Fast enough to recompute your entire UI every frame.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
) )
clay.Text( clay.Text(
clay.ID("PerformanceTextSubTitle", 2),
"Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free.", "Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
) )
clay.Text( clay.Text(
clay.ID("PerformanceTextSubTitle", 3),
"Simplify animations and reactive UI design by avoiding the standard performance hacks.", "Simplify animations and reactive UI design by avoiding the standard performance hacks.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
) )
} }
if clay.Container(clay.ID("PerformanceRightImageOuter"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) { if clay.UI(clay.ID("PerformanceRightImageOuter"))({ layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.Border( if clay.UI(clay.ID("PerformanceRightBorder"))({
clay.ID("PerformanceRightBorder"), layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(400) } },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(400)}}), border = { COLOR_LIGHT, {2, 2, 2, 2, 2} },
clay.BorderConfigAll({width = 2, color = COLOR_LIGHT}), }) {
) { if clay.UI(clay.ID("AnimationDemoContainerLeft"))({
if clay.Rectangle( layout = { sizing = { clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
clay.ID("AnimationDemoContainerLeft"), backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue),
clay.Layout({sizing = {clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {16, 16}}), }) {
clay.RectangleConfig({color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)}), clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
) {
clay.Text(clay.ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
} }
if clay.Rectangle( if clay.UI(clay.ID("AnimationDemoContainerRight"))({
clay.ID("AnimationDemoContainerRight"), layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {16, 16}}), backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue),
clay.RectangleConfig({color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)}), }) {
) { clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
clay.Text(clay.ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
} }
} }
} }
} }
HighPerformancePageDesktop :: proc(lerpValue: f32) { HighPerformancePageDesktop :: proc(lerpValue: f32) {
if clay.Rectangle( if clay.UI(clay.ID("PerformanceDesktop"))({
clay.ID("PerformanceDesktop"), layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { 82, 82, 32, 32 }, childGap = 64 },
clay.Layout( backgroundColor = COLOR_RED,
{sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 82, y = 32}, childGap = 64}, }) {
),
clay.RectangleConfig({color = COLOR_RED}),
) {
HighPerformancePage(lerpValue, {fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingPercent(0.5)) HighPerformancePage(lerpValue, {fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingPercent(0.5))
} }
} }
HighPerformancePageMobile :: proc(lerpValue: f32) { HighPerformancePageMobile :: proc(lerpValue: f32) {
if clay.Rectangle( if clay.UI(clay.ID("PerformanceMobile"))({
clay.ID("PerformanceMobile"), layout = {
clay.Layout( layoutDirection = .TopToBottom,
{ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
layoutDirection = .TOP_TO_BOTTOM, childAlignment = { x = .Center, y = .Center },
sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, padding = { 16, 16, 32, 32 },
childAlignment = {x = .CENTER, y = .CENTER}, childGap = 32,
padding = {x = 16, y = 32}, },
childGap = 32, backgroundColor = COLOR_RED,
}, }) {
),
clay.RectangleConfig({color = COLOR_RED}),
) {
HighPerformancePage(lerpValue, {fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_LIGHT}, clay.SizingGrow({})) HighPerformancePage(lerpValue, {fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_LIGHT}, clay.SizingGrow({}))
} }
} }
RendererButtonActive :: proc(id: clay.ElementId, index: i32, text: string) { RendererButtonActive :: proc(index: i32, $text: string) {
if clay.Rectangle( if clay.UI()({
id, layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
clay.Layout({sizing = {width = clay.SizingFixed(300)}, padding = {16, 16}}), backgroundColor = COLOR_RED,
clay.RectangleConfig({color = clay.PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, cornerRadius = clay.CornerRadiusAll(10)}), cornerRadius = clay.CornerRadiusAll(10)
) { }) {
clay.Text(clay.ID("RendererButtonActiveText"), text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_LIGHT})) clay.Text(text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_LIGHT}))
} }
} }
RendererButtonInactive :: proc(id: clay.ElementId, index: u32, text: string) { RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.Border(id, clay.Layout({}), clay.BorderConfigOutsideRadius({2, COLOR_RED}, 10)) { if clay.UI()({ border = border2pxRed }) {
if clay.Rectangle( if clay.UI(clay.ID("RendererButtonInactiveInner", index))({
clay.ID("RendererButtonInactiveInner", index), layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
clay.Layout({sizing = {width = clay.SizingFixed(300)}, padding = {16, 16}}), backgroundColor = COLOR_LIGHT,
clay.RectangleConfig({color = clay.PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, cornerRadius = clay.CornerRadiusAll(10)}), cornerRadius = clay.CornerRadiusAll(10)
) { }) {
clay.Text(clay.ID("RendererButtonInactiveText", index), text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED})) clay.Text(text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}))
} }
} }
} }
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) { RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.Container(clay.ID("RendererLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) { if clay.UI(clay.ID("RendererLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(clay.ID("RendererTextTitle"), "Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig)) clay.Text("Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig))
if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {} if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
clay.Text( clay.Text(
clay.ID("RendererTextSubTitle", 1),
"Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE.", "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}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
) )
clay.Text( clay.Text(
clay.ID("RendererTextSubTitle", 2),
"Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more.", "Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
) )
clay.Text( clay.Text(
clay.ID("RendererTextSubTitle", 3),
"There's even an HTML renderer - you're looking at it right now!", "There's even an HTML renderer - you're looking at it right now!",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}), clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
) )
} }
if clay.Container( if clay.UI(clay.ID("RendererRightText"))({
clay.ID("RendererRightText"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center }, layoutDirection = .TopToBottom, childGap = 16 },
clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}, layoutDirection = .TOP_TO_BOTTOM, childGap = 16}), }) {
) { clay.Text("Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}))
clay.Text(clay.ID("RendererTextRightTitle"), "Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE})) if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {} RendererButtonActive(0, "Raylib Renderer")
RendererButtonActive(clay.ID("RendererSelectButtonActive", 0), 0, "Raylib Renderer")
} }
} }
RendererPageDesktop :: proc() { RendererPageDesktop :: proc() {
if clay.Container( if clay.UI(clay.ID("RendererPageDesktop"))({
clay.ID("RendererPageDesktop"), layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, childAlignment = {y = .CENTER}, padding = {x = 50}}), }) {
) { if clay.UI(clay.ID("RendererPage"))({
if clay.Border( layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
clay.ID("RendererPage"), border = { COLOR_RED, { left = 2, right = 2 } },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, childAlignment = {y = .CENTER}, padding = {32, 32}, childGap = 32}), }) {
clay.BorderConfig({left = {2, COLOR_RED}, right = {2, COLOR_RED}}),
) {
RendererPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5)) RendererPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
} }
} }
} }
RendererPageMobile :: proc() { RendererPageMobile :: proc() {
if clay.Rectangle( if clay.UI(clay.ID("RendererMobile"))({
clay.ID("RendererMobile"), layout = {
clay.Layout( layoutDirection = .TopToBottom,
{ sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
layoutDirection = .TOP_TO_BOTTOM, childAlignment = { x = .Center, y = .Center },
sizing = {clay.SizingGrow({}), clay.SizingFit({min = cast(f32)windowHeight - 50})}, padding = { 16, 16, 32, 32 },
childAlignment = {x = .CENTER, y = .CENTER}, childGap = 32,
padding = {x = 16, y = 32}, },
childGap = 32, backgroundColor = COLOR_LIGHT,
}, }) {
),
clay.RectangleConfig({color = COLOR_LIGHT}),
) {
RendererPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({})) RendererPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({}))
} }
} }
@ -430,67 +391,56 @@ animationLerpValue: f32 = -1.0
createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) { createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
mobileScreen := windowWidth < 750 mobileScreen := windowWidth < 750
clay.BeginLayout() clay.BeginLayout()
if clay.Rectangle( if clay.UI(clay.ID("OuterContainer"))({
clay.ID("OuterContainer"), layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow(), clay.SizingGrow() } },
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), backgroundColor = COLOR_LIGHT,
clay.RectangleConfig({color = COLOR_LIGHT}), }) {
) { if clay.UI(clay.ID("Header"))({
if clay.Container( layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(50) }, childAlignment = { y = .Center }, childGap = 24, padding = { left = 32, right = 32 } },
clay.ID("Header"), }) {
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(50)}, childAlignment = {y = .CENTER}, childGap = 24, padding = {x = 32}}), clay.Text("Clay", &headerTextConfig)
) { if clay.UI()({ layout = { sizing = { width = clay.SizingGrow() } } }) {}
clay.Text(clay.ID("Logo"), "Clay", &headerTextConfig)
if clay.Container(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) {}
if (!mobileScreen) { if (!mobileScreen) {
if clay.Rectangle(clay.ID("LinkExamplesOuter"), clay.Layout({}), clay.RectangleConfig({color = {0, 0, 0, 0}})) { if clay.UI(clay.ID("LinkExamplesOuter"))({ backgroundColor = {0, 0, 0, 0} }) {
clay.Text(clay.ID("LinkExamplesText"), "Examples", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}})) clay.Text("Examples", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
} }
if clay.Rectangle(clay.ID("LinkDocsOuter"), clay.Layout({}), clay.RectangleConfig({color = {0, 0, 0, 0}})) { if clay.UI(clay.ID("LinkDocsOuter"))({ backgroundColor = {0, 0, 0, 0} }) {
clay.Text(clay.ID("LinkDocsText"), "Docs", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}})) clay.Text("Docs", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
} }
} }
githubButtonId: clay.ElementId = clay.ID("HeaderButtonGithub") if clay.UI(clay.ID("LinkGithubOuter"))({
if clay.Border(clay.ID("LinkGithubOuter"), clay.Layout({}), clay.BorderConfigOutsideRadius({2, COLOR_RED}, 10)) { layout = { padding = { 16, 16, 6, 6 } },
if clay.Rectangle( border = border2pxRed,
githubButtonId, backgroundColor = clay.Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
clay.Layout({padding = {16, 6}}), cornerRadius = clay.CornerRadiusAll(10)
clay.RectangleConfig({cornerRadius = clay.CornerRadiusAll(10), color = clay.PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT}), }) {
) { clay.Text("Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
clay.Text(clay.ID("LinkGithubText"), "Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
} }
} }
if clay.Rectangle(clay.ID("TopBorder1"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_5})) {} if clay.UI(clay.ID("TopBorder1"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
if clay.Rectangle(clay.ID("TopBorder2"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_4})) {} if clay.UI(clay.ID("TopBorder2"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
if clay.Rectangle(clay.ID("TopBorder3"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_3})) {} if clay.UI(clay.ID("TopBorder3"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
if clay.Rectangle(clay.ID("TopBorder4"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_2})) {} if clay.UI(clay.ID("TopBorder4"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
if clay.Rectangle(clay.ID("TopBorder5"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(4)}}), clay.RectangleConfig({color = COLOR_TOP_BORDER_1})) {} if clay.UI(clay.ID("TopBorder5"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
if clay.Rectangle( if clay.UI(clay.ID("ScrollContainerBackgroundRectangle"))({
clay.ID("ScrollContainerBackgroundRectangle"), clip = { vertical = true, childOffset = clay.GetScrollOffset() },
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, layoutDirection = clay.LayoutDirection.TopToBottom },
clay.RectangleConfig({color = COLOR_LIGHT}), backgroundColor = COLOR_LIGHT,
) { border = { COLOR_RED, { betweenChildren = 2} },
if clay.Scroll(clay.ID("OuterScrollContainer"), clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}}), clay.ScrollConfig({vertical = true})) { }) {
if clay.Border( if (!mobileScreen) {
clay.ID("ScrollContainerInner"), LandingPageDesktop()
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingGrow({})}}), FeatureBlocksDesktop()
clay.BorderConfig({betweenChildren = {2, COLOR_RED}}), DeclarativeSyntaxPageDesktop()
) { HighPerformancePageDesktop(lerpValue)
if (!mobileScreen) { RendererPageDesktop()
LandingPageDesktop() } else {
FeatureBlocksDesktop() LandingPageMobile()
DeclarativeSyntaxPageDesktop() FeatureBlocksMobile()
HighPerformancePageDesktop(lerpValue) DeclarativeSyntaxPageMobile()
RendererPageDesktop() HighPerformancePageMobile(lerpValue)
} else { RendererPageMobile()
LandingPageMobile()
FeatureBlocksMobile()
DeclarativeSyntaxPageMobile()
HighPerformancePageMobile(lerpValue)
RendererPageMobile()
}
}
} }
} }
} }
@ -498,21 +448,27 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
} }
loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) { loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) {
raylibFonts[fontId] = RaylibFont { assign_at(&raylib_fonts,fontId,Raylib_Font{
font = raylib.LoadFontEx(path, cast(i32)fontSize * 2, nil, 0), font = raylib.LoadFontEx(path, cast(i32)fontSize * 2, nil, 0),
fontId = cast(u16)fontId, fontId = cast(u16)fontId,
})
raylib.SetTextureFilter(raylib_fonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
}
errorHandler :: proc "c" (errorData: clay.ErrorData) {
if (errorData.errorType == clay.ErrorType.DuplicateId) {
// etc
} }
raylib.SetTextureFilter(raylibFonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
} }
main :: proc() { main :: proc() {
minMemorySize: u32 = clay.MinMemorySize() minMemorySize: c.size_t = cast(c.size_t)clay.MinMemorySize()
memory := make([^]u8, minMemorySize) memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory) arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.SetMeasureTextFunction(measureText) clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}) clay.SetMeasureTextFunction(measure_text, 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.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
raylib.SetTargetFPS(raylib.GetMonitorRefreshRate(0)) raylib.SetTargetFPS(raylib.GetMonitorRefreshRate(0))
loadFont(FONT_ID_TITLE_56, 56, "resources/Calistoga-Regular.ttf") loadFont(FONT_ID_TITLE_56, 56, "resources/Calistoga-Regular.ttf")
@ -526,12 +482,12 @@ main :: proc() {
loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf") loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf") loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf")
syntaxImage = raylib.LoadTextureFromImage(raylib.LoadImage("resources/declarative.png")) syntaxImage = raylib.LoadTexture("resources/declarative.png")
checkImage1 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_1.png")) checkImage1 = raylib.LoadTexture("resources/check_1.png")
checkImage2 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_2.png")) checkImage2 = raylib.LoadTexture("resources/check_2.png")
checkImage3 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_3.png")) checkImage3 = raylib.LoadTexture("resources/check_3.png")
checkImage4 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_4.png")) checkImage4 = raylib.LoadTexture("resources/check_4.png")
checkImage5 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_5.png")) checkImage5 = raylib.LoadTexture("resources/check_5.png")
debugModeEnabled: bool = false debugModeEnabled: bool = false
@ -553,7 +509,7 @@ main :: proc() {
clay.SetLayoutDimensions({cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}) clay.SetLayoutDimensions({cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
renderCommands: clay.ClayArray(clay.RenderCommand) = createLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue)) renderCommands: clay.ClayArray(clay.RenderCommand) = createLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue))
raylib.BeginDrawing() raylib.BeginDrawing()
clayRaylibRender(&renderCommands) clay_raylib_render(&renderCommands)
raylib.EndDrawing() raylib.EndDrawing()
} }
} }

View file

@ -3,183 +3,199 @@ package main
import clay "../../clay-odin" import clay "../../clay-odin"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
import "vendor:raylib" import rl "vendor:raylib"
RaylibFont :: struct { Raylib_Font :: struct {
fontId: u16, fontId: u16,
font: raylib.Font, font: rl.Font,
} }
clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color { clay_color_to_rl_color :: proc(color: clay.Color) -> rl.Color {
return raylib.Color{cast(u8)color.r, cast(u8)color.g, cast(u8)color.b, cast(u8)color.a} return {u8(color.r), u8(color.g), u8(color.b), u8(color.a)}
} }
raylibFonts := [10]RaylibFont{} raylib_fonts := [dynamic]Raylib_Font{}
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions { measure_text :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions {
// Measure string size for Font line_width: f32 = 0
textSize: clay.Dimensions = {0, 0}
maxTextWidth: f32 = 0 font := raylib_fonts[config.fontId].font
lineTextWidth: f32 = 0
textHeight := cast(f32)config.fontSize for i in 0 ..< text.length {
fontToUse := raylibFonts[config.fontId].font glyph_index := text.chars[i] - 32
for i in 0 ..< int(text.length) { glyph := font.glyphs[glyph_index]
if (text.chars[i] == '\n') {
maxTextWidth = max(maxTextWidth, lineTextWidth) if glyph.advanceX != 0 {
lineTextWidth = 0 line_width += f32(glyph.advanceX)
continue
}
index := cast(i32)text.chars[i] - 32
if (fontToUse.glyphs[index].advanceX != 0) {
lineTextWidth += cast(f32)fontToUse.glyphs[index].advanceX
} else { } else {
lineTextWidth += (fontToUse.recs[index].width + cast(f32)fontToUse.glyphs[index].offsetX) line_width += font.recs[glyph_index].width + f32(glyph.offsetX)
} }
} }
maxTextWidth = max(maxTextWidth, lineTextWidth) return {width = line_width / 2, height = f32(config.fontSize)}
textSize.width = maxTextWidth / 2
textSize.height = textHeight
return textSize
} }
clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) { clay_raylib_render :: proc(render_commands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) {
for i in 0 ..< int(renderCommands.length) { for i in 0 ..< render_commands.length {
renderCommand := clay.RenderCommandArray_Get(renderCommands, cast(i32)i) render_command := clay.RenderCommandArray_Get(render_commands, i)
boundingBox := renderCommand.boundingBox bounds := render_command.boundingBox
switch (renderCommand.commandType) {
case clay.RenderCommandType.None: switch render_command.commandType {
{} case .None: // None
case clay.RenderCommandType.Text: case .Text:
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator config := render_command.renderData.text
text := string(renderCommand.text.chars[:renderCommand.text.length])
cloned := strings.clone_to_cstring(text, allocator) text := string(config.stringContents.chars[:config.stringContents.length])
fontToUse: raylib.Font = raylibFonts[renderCommand.config.textElementConfig.fontId].font
raylib.DrawTextEx( // Raylib uses C strings instead of Odin strings, so we need to clone
fontToUse, // Assume this will be freed elsewhere since we default to the temp allocator
cloned, cstr_text := strings.clone_to_cstring(text, allocator)
raylib.Vector2{boundingBox.x, boundingBox.y},
cast(f32)renderCommand.config.textElementConfig.fontSize, font := raylib_fonts[config.fontId].font
cast(f32)renderCommand.config.textElementConfig.letterSpacing, rl.DrawTextEx(font, cstr_text, {bounds.x, bounds.y}, f32(config.fontSize), f32(config.letterSpacing), clay_color_to_rl_color(config.textColor))
clayColorToRaylibColor(renderCommand.config.textElementConfig.textColor), case .Image:
) config := render_command.renderData.image
case clay.RenderCommandType.Image: tint := config.backgroundColor
// TODO image handling if tint == 0 {
imageTexture := cast(^raylib.Texture2D)renderCommand.config.imageElementConfig.imageData tint = {255, 255, 255, 255}
raylib.DrawTextureEx(imageTexture^, raylib.Vector2{boundingBox.x, boundingBox.y}, 0, boundingBox.width / cast(f32)imageTexture.width, raylib.WHITE)
case clay.RenderCommandType.ScissorStart:
raylib.BeginScissorMode(
cast(i32)math.round(boundingBox.x),
cast(i32)math.round(boundingBox.y),
cast(i32)math.round(boundingBox.width),
cast(i32)math.round(boundingBox.height),
)
case clay.RenderCommandType.ScissorEnd:
raylib.EndScissorMode()
case clay.RenderCommandType.Rectangle:
config: ^clay.RectangleElementConfig = renderCommand.config.rectangleElementConfig
if (config.cornerRadius.topLeft > 0) {
radius: f32 = (config.cornerRadius.topLeft * 2) / min(boundingBox.width, boundingBox.height)
raylib.DrawRectangleRounded(raylib.Rectangle{boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, radius, 8, clayColorToRaylibColor(config.color))
} else {
raylib.DrawRectangle(cast(i32)boundingBox.x, cast(i32)boundingBox.y, cast(i32)boundingBox.width, cast(i32)boundingBox.height, clayColorToRaylibColor(config.color))
} }
case clay.RenderCommandType.Border:
config := renderCommand.config.borderElementConfig imageTexture := (^rl.Texture2D)(config.imageData)
rl.DrawTextureEx(imageTexture^, {bounds.x, bounds.y}, 0, bounds.width / f32(imageTexture.width), clay_color_to_rl_color(tint))
case .ScissorStart:
rl.BeginScissorMode(i32(math.round(bounds.x)), i32(math.round(bounds.y)), i32(math.round(bounds.width)), i32(math.round(bounds.height)))
case .ScissorEnd:
rl.EndScissorMode()
case .Rectangle:
config := render_command.renderData.rectangle
if config.cornerRadius.topLeft > 0 {
radius: f32 = (config.cornerRadius.topLeft * 2) / min(bounds.width, bounds.height)
draw_rect_rounded(bounds.x, bounds.y, bounds.width, bounds.height, radius, config.backgroundColor)
} else {
draw_rect(bounds.x, bounds.y, bounds.width, bounds.height, config.backgroundColor)
}
case .Border:
config := render_command.renderData.border
// Left border // Left border
if (config.left.width > 0) { if config.width.left > 0 {
raylib.DrawRectangle( draw_rect(
cast(i32)math.round(boundingBox.x), bounds.x,
cast(i32)math.round(boundingBox.y + config.cornerRadius.topLeft), bounds.y + config.cornerRadius.topLeft,
cast(i32)config.left.width, f32(config.width.left),
cast(i32)math.round(boundingBox.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft), bounds.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft,
clayColorToRaylibColor(config.left.color), config.color,
) )
} }
// Right border // Right border
if (config.right.width > 0) { if config.width.right > 0 {
raylib.DrawRectangle( draw_rect(
cast(i32)math.round(boundingBox.x + boundingBox.width - cast(f32)config.right.width), bounds.x + bounds.width - f32(config.width.right),
cast(i32)math.round(boundingBox.y + config.cornerRadius.topRight), bounds.y + config.cornerRadius.topRight,
cast(i32)config.right.width, f32(config.width.right),
cast(i32)math.round(boundingBox.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight), bounds.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight,
clayColorToRaylibColor(config.right.color), config.color,
) )
} }
// Top border // Top border
if (config.top.width > 0) { if config.width.top > 0 {
raylib.DrawRectangle( draw_rect(
cast(i32)math.round(boundingBox.x + config.cornerRadius.topLeft), bounds.x + config.cornerRadius.topLeft,
cast(i32)math.round(boundingBox.y), bounds.y,
cast(i32)math.round(boundingBox.width - config.cornerRadius.topLeft - config.cornerRadius.topRight), bounds.width - config.cornerRadius.topLeft - config.cornerRadius.topRight,
cast(i32)config.top.width, f32(config.width.top),
clayColorToRaylibColor(config.top.color), config.color,
) )
} }
// Bottom border // Bottom border
if (config.bottom.width > 0) { if config.width.bottom > 0 {
raylib.DrawRectangle( draw_rect(
cast(i32)math.round(boundingBox.x + config.cornerRadius.bottomLeft), bounds.x + config.cornerRadius.bottomLeft,
cast(i32)math.round(boundingBox.y + boundingBox.height - cast(f32)config.bottom.width), bounds.y + bounds.height - f32(config.width.bottom),
cast(i32)math.round(boundingBox.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight), bounds.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight,
cast(i32)config.bottom.width, f32(config.width.bottom),
clayColorToRaylibColor(config.bottom.color), config.color,
) )
} }
if (config.cornerRadius.topLeft > 0) {
raylib.DrawRing( // Rounded Borders
raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.topLeft), math.round(boundingBox.y + config.cornerRadius.topLeft)}, if config.cornerRadius.topLeft > 0 {
math.round(config.cornerRadius.topLeft - cast(f32)config.top.width), draw_arc(
bounds.x + config.cornerRadius.topLeft,
bounds.y + config.cornerRadius.topLeft,
config.cornerRadius.topLeft - f32(config.width.top),
config.cornerRadius.topLeft, config.cornerRadius.topLeft,
180, 180,
270, 270,
10, config.color,
clayColorToRaylibColor(config.top.color),
) )
} }
if (config.cornerRadius.topRight > 0) { if config.cornerRadius.topRight > 0 {
raylib.DrawRing( draw_arc(
raylib.Vector2{math.round(boundingBox.x + boundingBox.width - config.cornerRadius.topRight), math.round(boundingBox.y + config.cornerRadius.topRight)}, bounds.x + bounds.width - config.cornerRadius.topRight,
math.round(config.cornerRadius.topRight - cast(f32)config.top.width), bounds.y + config.cornerRadius.topRight,
config.cornerRadius.topRight - f32(config.width.top),
config.cornerRadius.topRight, config.cornerRadius.topRight,
270, 270,
360, 360,
10, config.color,
clayColorToRaylibColor(config.top.color),
) )
} }
if (config.cornerRadius.bottomLeft > 0) { if config.cornerRadius.bottomLeft > 0 {
raylib.DrawRing( draw_arc(
raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.bottomLeft), math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft)}, bounds.x + config.cornerRadius.bottomLeft,
math.round(config.cornerRadius.bottomLeft - cast(f32)config.top.width), bounds.y + bounds.height - config.cornerRadius.bottomLeft,
config.cornerRadius.bottomLeft - f32(config.width.top),
config.cornerRadius.bottomLeft, config.cornerRadius.bottomLeft,
90, 90,
180, 180,
10, config.color,
clayColorToRaylibColor(config.bottom.color),
) )
} }
if (config.cornerRadius.bottomRight > 0) { if config.cornerRadius.bottomRight > 0 {
raylib.DrawRing( draw_arc(
raylib.Vector2 { bounds.x + bounds.width - config.cornerRadius.bottomRight,
math.round(boundingBox.x + boundingBox.width - config.cornerRadius.bottomRight), bounds.y + bounds.height - config.cornerRadius.bottomRight,
math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomRight), config.cornerRadius.bottomRight - f32(config.width.bottom),
},
math.round(config.cornerRadius.bottomRight - cast(f32)config.bottom.width),
config.cornerRadius.bottomRight, config.cornerRadius.bottomRight,
0.1, 0.1,
90, 90,
10, config.color,
clayColorToRaylibColor(config.bottom.color),
) )
} }
case clay.RenderCommandType.Custom: case clay.RenderCommandType.Custom:
// Implement custom element rendering here // Implement custom element rendering here
} }
} }
} }
// Helper procs, mainly for repeated conversions
@(private = "file")
draw_arc :: proc(x, y: f32, inner_rad, outer_rad: f32,start_angle, end_angle: f32, color: clay.Color){
rl.DrawRing(
{math.round(x),math.round(y)},
math.round(inner_rad),
outer_rad,
start_angle,
end_angle,
10,
clay_color_to_rl_color(color),
)
}
@(private = "file")
draw_rect :: proc(x, y, w, h: f32, color: clay.Color) {
rl.DrawRectangle(
i32(math.round(x)),
i32(math.round(y)),
i32(math.round(w)),
i32(math.round(h)),
clay_color_to_rl_color(color)
)
}
@(private = "file")
draw_rect_rounded :: proc(x,y,w,h: f32, radius: f32, color: clay.Color){
rl.DrawRectangleRounded({x,y,w,h},radius,8,clay_color_to_rl_color(color))
}

2
bindings/rust/README Normal file
View file

@ -0,0 +1,2 @@
https://github.com/clay-ui-rs/clay
https://crates.io/crates/clay-layout

2
bindings/zig/README Normal file
View file

@ -0,0 +1,2 @@
https://codeberg.org/Zettexe/clay-zig
https://github.com/johan0A/clay-zig-bindings

5960
clay.h

File diff suppressed because it is too large Load diff

32
cmake/FindCairo.cmake Normal file
View file

@ -0,0 +1,32 @@
# Defines:
# CAIRO_FOUND - System has Cairo
# CAIRO_INCLUDE_DIRS - Cairo include directories
# CAIRO_LIBRARY - Cairo library
# Cairo::Cairo - Imported target
find_path(CAIRO_INCLUDE_DIRS
NAMES cairo/cairo.h
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES include
)
find_library(CAIRO_LIBRARY
NAMES cairo
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES lib lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Cairo
REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS
)
if(Cairo_FOUND AND NOT TARGET Cairo::Cairo)
add_library(Cairo::Cairo UNKNOWN IMPORTED)
set_target_properties(Cairo::Cairo PROPERTIES
IMPORTED_LOCATION "${CAIRO_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}"
)
endif()
mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY)

View file

@ -0,0 +1,59 @@
cmake_minimum_required(VERSION 3.27)
project(SDL2_video_demo C)
set(CMAKE_C_STANDARD 99)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
SDL2
GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git"
GIT_TAG "release-2.30.10"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2)
FetchContent_Declare(
SDL2_ttf
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_ttf.git"
GIT_TAG "release-2.22.0"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2_ttf)
FetchContent_Declare(
SDL2_image
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
GIT_TAG "release-2.8.4"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2_image)
add_executable(SDL2_video_demo main.c)
target_compile_options(SDL2_video_demo PUBLIC)
target_include_directories(SDL2_video_demo PUBLIC .)
target_link_libraries(SDL2_video_demo PUBLIC
SDL2::SDL2main
SDL2::SDL2-static
SDL2_ttf::SDL2_ttf-static
SDL2_image::SDL2_image-static
)
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
add_custom_command(
TARGET SDL2_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,173 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/SDL2/clay_renderer_SDL2.c"
#include <SDL.h>
#include <SDL_ttf.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include "../shared-layouts/clay-video-demo.c"
SDL_Surface *sample_image;
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());
return 1;
}
if (TTF_Init() < 0) {
fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError());
return 1;
}
if (IMG_Init(IMG_INIT_PNG) < 0) {
fprintf(stderr, "Error: could not initialize IMG: %s\n", IMG_GetError());
return 1;
}
TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 16);
if (!font) {
fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError());
return 1;
}
SDL2_Font fonts[1] = {};
fonts[FONT_ID_BODY_16] = (SDL2_Font) {
.fontId = FONT_ID_BODY_16,
.font = font,
};
sample_image = IMG_Load("resources/sample.png");
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
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));
int windowWidth = 0;
int windowHeight = 0;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL2_MeasureText, &fonts);
Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
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;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT: { goto quit; }
case SDL_MOUSEWHEEL: {
scrollDelta.x = event.wheel.x;
scrollDelta.y = event.wheel.y;
break;
}
}
}
LAST = NOW;
NOW = SDL_GetPerformanceCounter();
deltaTime = (double)((NOW - LAST)*1000 / (double)SDL_GetPerformanceFrequency() );
printf("%f\n", deltaTime);
int mouseX = 0;
int mouseY = 0;
Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
Clay_Vector2 mousePosition = (Clay_Vector2){ (float)mouseX, (float)mouseY };
Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1));
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
deltaTime
);
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);
}
quit:
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
TTF_Quit();
SDL_Quit();
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View file

@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.27)
# Project setup
project(clay_examples_sdl3_simple_demo C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# Download SDL3
FetchContent_Declare(
SDL
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-3.2.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL via FetchContent")
FetchContent_MakeAvailable(SDL)
set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Download SDL_ttf
FetchContent_Declare(
SDL_ttf
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
GIT_TAG release-3.2.2
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL_ttf via FetchContent")
FetchContent_MakeAvailable(SDL_ttf)
set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Download SDL_image
FetchContent_Declare(
SDL_image
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
GIT_TAG release-3.2.0
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(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources
)

View file

@ -0,0 +1,232 @@
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include <stdio.h>
#include "../../renderers/SDL3/clay_renderer_SDL3.c"
#include "../shared-layouts/clay-video-demo.c"
static const Uint32 FONT_ID = 0;
static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
typedef struct app_state {
SDL_Window *window;
Clay_SDL3RendererData rendererData;
ClayVideoDemo_Data demoData;
} AppState;
SDL_Texture *sample_image;
bool show_demo = true;
static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
{
TTF_Font **fonts = userData;
TTF_Font *font = fonts[config->fontId];
int width, height;
TTF_SetFontSize(font, config->fontSize);
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError());
}
return (Clay_Dimensions) { (float) width, (float) height };
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
Clay_RenderCommandArray ClayImageSample_CreateLayout() {
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
CLAY(CLAY_ID("OuterContainer"), {
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(16),
.childGap = 16
}
}) {
CLAY(CLAY_ID("SampleImage"), {
.layout = {
.sizing = layoutExpand
},
.aspectRatio = { 23.0 / 42.0 },
.image = {
.imageData = sample_image,
}
});
}
return Clay_EndLayout();
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
(void) argc;
(void) argv;
if (!TTF_Init()) {
return SDL_APP_FAILURE;
}
AppState *state = SDL_calloc(1, sizeof(AppState));
if (!state) {
return SDL_APP_FAILURE;
}
*appstate = state;
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->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;
}
state->rendererData.fonts[FONT_ID] = font;
sample_image = IMG_LoadTexture(state->rendererData.renderer, "resources/sample.png");
if (!sample_image) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load image: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
/* Initialize Clay */
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = (Clay_Arena) {
.memory = SDL_malloc(totalMemorySize),
.capacity = totalMemorySize
};
int width, height;
SDL_GetWindowSize(state->window, &width, &height);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts);
state->demoData = ClayVideoDemo_Initialize();
*appstate = state;
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
SDL_AppResult ret_val = SDL_APP_CONTINUE;
switch (event->type) {
case SDL_EVENT_QUIT:
ret_val = SDL_APP_SUCCESS;
break;
case SDL_EVENT_KEY_UP:
if (event->key.scancode == SDL_SCANCODE_SPACE) {
show_demo = !show_demo;
}
break;
case SDL_EVENT_WINDOW_RESIZED:
Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 });
break;
case SDL_EVENT_MOUSE_MOTION:
Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
event->motion.state & SDL_BUTTON_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->wheel.x, event->wheel.y }, 0.01f);
break;
default:
break;
};
return ret_val;
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
AppState *state = appstate;
Clay_RenderCommandArray render_commands = (show_demo
? ClayVideoDemo_CreateLayout(&state->demoData)
: ClayImageSample_CreateLayout()
);
SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255);
SDL_RenderClear(state->rendererData.renderer);
SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands);
SDL_RenderPresent(state->rendererData.renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
(void) result;
if (result != SDL_APP_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run");
}
AppState *state = appstate;
if (sample_image) {
SDL_DestroyTexture(sample_image);
}
if (state) {
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View file

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_cairo_pdf_rendering C)
set(CMAKE_C_STANDARD 99)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake")
add_executable(clay_examples_cairo_pdf_rendering main.c)
find_package(Cairo REQUIRED)
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS})
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC Cairo::Cairo)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
add_custom_command(
TARGET clay_examples_cairo_pdf_rendering POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,171 @@
// Copyright (c) 2024 Justin Andreas Lacoste (@27justin)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software in a
// product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
// SPDX-License-Identifier: Zlib
#include <stdlib.h>
// The renderer includes clay.h while also providing the
// CLAY_IMPLEMENTATION
#include "../../renderers/cairo/clay_renderer_cairo.c"
// cairo-pdf, though this is optional and not required if you,
// e.g. render PNGs.
#include <cairo/cairo-pdf.h>
const uint16_t FONT_CALLISTOGA = 0;
const uint16_t FONT_QUICKSAND = 0;
// Layout the first page.
void Layout() {
static Clay_Color PRIMARY = { 0xa8, 0x42, 0x1c, 255 };
static Clay_Color BACKGROUND = { 0xF4, 0xEB, 0xE6, 255 };
static Clay_Color ACCENT = { 0xFA, 0xE0, 0xD4, 255 };
CLAY_AUTO_ID({
.layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) },
.layoutDirection = CLAY_TOP_TO_BOTTOM },
.backgroundColor = BACKGROUND
}) {
CLAY(CLAY_ID("PageMargins"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) },
.padding = { 70, 70, 50, 50 }, // Some nice looking page margins
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 10}
}) {
// Section Title
CLAY_TEXT(CLAY_STRING("Features Overview"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .textColor = PRIMARY, .fontSize = 24 }));
// Feature Box
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }, .childGap = 10 }}) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }}, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(12) }) {
CLAY_AUTO_ID({ .layout = {.padding = CLAY_PADDING_ALL(20), .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) {
CLAY_TEXT(CLAY_STRING("- High performance"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Declarative syntax"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Flexbox-style responsive layout"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Single .h file for C/C++"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- And now with cairo!"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
}
}
CLAY_AUTO_ID({
.layout = {
.sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)},
.padding = CLAY_PADDING_ALL(10),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER },
.childGap = 4
},
.backgroundColor = ACCENT,
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Profile picture
CLAY_AUTO_ID({ .layout = {
.sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)},
.padding = { 30, 30, 0, 0 },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }},
.border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10
}) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .imageData = "resources/check.png" }});
}
}
}
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(16) } }});
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childGap = 10, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) {
CLAY_TEXT(CLAY_STRING("Cairo"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .fontSize = 24, .textColor = PRIMARY }));
CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(10) }, .backgroundColor = ACCENT, .cornerRadius = 10 }) {
CLAY_TEXT(CLAY_STRING("Officiis quia quia qui inventore ratione voluptas et. Quidem sunt unde similique. Qui est et exercitationem cumque harum illum. Numquam placeat aliquid quo voluptatem. "
"Deleniti saepe nihil exercitationem nemo illo. Consequatur beatae repellat provident similique. Provident qui exercitationem deserunt sapiente. Quam qui dolor corporis odit. "
"Assumenda corrupti sunt culpa pariatur. Vero sit ut minima. In est consequatur minus et cum sint illum aperiam. Qui ipsa quas nisi omnis aut quia nobis. "
"Corporis deserunt eum mollitia modi rerum voluptas. Expedita non ab esse. Sit voluptates eos voluptatem labore aspernatur quia eum. Modi cumque atque non. Sunt officiis corrupti neque ut inventore excepturi rem minima. Possimus sed soluta qui ea aut ipsum laborum fugit. "
"Voluptate eum consectetur non. Quo autem voluptate soluta atque dolorum maxime. Officiis inventore omnis eveniet beatae ipsa optio. Unde voluptatum ut autem quia sit sit et. Ut inventore qui quia totam consequatur. Sit ea consequatur omnis rerum nulla aspernatur deleniti."), CLAY_TEXT_CONFIG({ .fontId = FONT_QUICKSAND, .fontSize = 16, .textColor = PRIMARY, .lineHeight = 16 }));
}
}
}
}
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
// First we set up our cairo surface.
// In this example we will use the PDF backend,
// but you should be able to use any of them.
// Guaranteed to be working are: PDF, PNG
// Create a PDF surface that is the same size as a DIN A4 sheet
// When using the PDF backend, cairo calculates in points (1 point == 1/72.0 inch)
double width = (21.0 / 2.54) * 72, // cm in points
height = (29.7 / 2.54) * 72;
cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height);
cairo_t *cr = cairo_create(surface);
cairo_surface_destroy(surface); // Drop reference
// Initialize internal global variable with `cr`.
// We require some kind of global reference to a valid
// cairo instance to properly measure text.
// Note that due to this, this interface is not thread-safe!
Clay_Cairo_Initialize(cr);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
// We initialize Clay with the same size
Clay_Initialize(clayMemory, (Clay_Dimensions) { width, height }, (Clay_ErrorHandler) { HandleClayErrors });
char** fonts = (char*[]) {
"Callistoga",
"Quicksand Semibold"
};
Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, fonts);
Clay_BeginLayout();
// Here you can now create the declarative clay layout.
// Moved into a separate function for brevity.
Layout();
Clay_RenderCommandArray commands = Clay_EndLayout();
// Pass our layout to the cairo backend
Clay_Cairo_Render(commands, fonts);
// To keep this example short, we will not emit a second page in the PDF.
// But to do so, you have to
// 1. cairo_show_page(cr)
// 2. Clay_BeginLayout();
// 3. Create your layout
// 4. commands = Clay_EndLayout();
// 5. Clay_Cairo_Render(commands);
cairo_destroy(cr);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.27)
project(clay_official_website C) project(clay_official_website C)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
add_executable(clay_official_website main.c) add_executable(clay_official_website main.c)
target_compile_options(clay_official_website PUBLIC -Wno-initializer-overrides) target_compile_options(clay_official_website PUBLIC)
target_include_directories(clay_official_website PUBLIC .) target_include_directories(clay_official_website PUBLIC .)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

View file

@ -1,5 +1,7 @@
mkdir -p build/clay \ mkdir -p build/clay \
&& clang \ && clang \
-Wall \
-Werror \
-Os \ -Os \
-DCLAY_WASM \ -DCLAY_WASM \
-mbulk-memory \ -mbulk-memory \

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View file

@ -0,0 +1,857 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preload" href="/clay/fonts/Calistoga-Regular.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/clay/fonts/Quicksand-Semibold.ttf" as="font" type="font/ttf" crossorigin>
<title>Clay - UI Layout Library</title>
<style>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
pointer-events: none;
background: rgb(244, 235, 230);
}
/* Import the font using @font-face */
@font-face {
font-family: 'Calistoga';
font-style: normal;
font-weight: 400;
src: url('/clay/fonts/Calistoga-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
src: url('/clay/fonts/Quicksand-Semibold.ttf') format('truetype');
}
body > canvas {
width: 100%;
height: 100%;
touch-action: none;
}
div, a, img {
position: absolute;
box-sizing: border-box;
-webkit-backface-visibility: hidden;
pointer-events: none;
}
a {
cursor: pointer;
pointer-events: all;
}
.text {
pointer-events: all;
white-space: pre;
}
/* TODO special exception for text selection in debug tools */
[id='2067877626'] > * {
pointer-events: none !important;
}
</style>
</head>
<script type="module">
const CLAY_RENDER_COMMAND_TYPE_NONE = 0;
const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1;
const CLAY_RENDER_COMMAND_TYPE_BORDER = 2;
const CLAY_RENDER_COMMAND_TYPE_TEXT = 3;
const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4;
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5;
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6;
const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7;
const GLOBAL_FONT_SCALING_FACTOR = 0.8;
let renderCommandSize = 0;
let scratchSpaceAddress = 8;
let heapSpaceAddress = 0;
let memoryDataView;
let textDecoder = new TextDecoder("utf-8");
let previousFrameTime;
let fontsById = [
'Quicksand',
'Calistoga',
'Quicksand',
'Quicksand',
'Quicksand',
];
let elementCache = {};
let imageCache = {};
let dimensionsDefinition = { type: 'struct', members: [
{name: 'width', type: 'float'},
{name: 'height', type: 'float'},
]};
let colorDefinition = { type: 'struct', members: [
{name: 'r', type: 'float' },
{name: 'g', type: 'float' },
{name: 'b', type: 'float' },
{name: 'a', type: 'float' },
]};
let stringDefinition = { type: 'struct', members: [
{name: 'isStaticallyAllocated', type: 'uint32_t'},
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let stringSliceDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderWidthDefinition = { type: 'struct', members: [
{name: 'left', type: 'uint16_t'},
{name: 'right', type: 'uint16_t'},
{name: 'top', type: 'uint16_t'},
{name: 'bottom', type: 'uint16_t'},
{name: 'betweenChildren', type: 'uint16_t'},
]};
let cornerRadiusDefinition = { type: 'struct', members: [
{name: 'topLeft', type: 'float'},
{name: 'topRight', type: 'float'},
{name: 'bottomLeft', type: 'float'},
{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' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'wrapMode', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: '_padding', type: 'uint16_t' },
]};
let textRenderDataDefinition = { type: 'struct', members: [
{ name: 'stringContents', ...stringSliceDefinition },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineHeight', type: 'uint16_t' },
]};
let rectangleRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
]};
let imageRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'imageData', type: 'uint32_t' },
]};
let customRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'customData', type: 'uint32_t' },
]};
let borderRenderDataDefinition = { type: 'struct', members: [
{ name: 'color', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'width', ...borderWidthDefinition },
{ name: 'padding', type: 'uint16_t'}
]};
let clipRenderDataDefinition = { type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
let customHTMLDataDefinition = { type: 'struct', members: [
{ name: 'link', ...stringDefinition },
{ name: 'cursorPointer', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: 'padding', type: 'uint16_t'}
]};
let renderCommandDefinition = {
name: 'Clay_RenderCommand',
type: 'struct',
members: [
{ name: 'boundingBox', type: 'struct', members: [
{ name: 'x', type: 'float' },
{ name: 'y', type: 'float' },
{ name: 'width', type: 'float' },
{ name: 'height', type: 'float' },
]},
{ name: 'renderData', type: 'union', members: [
{ name: 'rectangle', ...rectangleRenderDataDefinition },
{ name: 'text', ...textRenderDataDefinition },
{ name: 'image', ...imageRenderDataDefinition },
{ name: 'custom', ...customRenderDataDefinition },
{ name: 'border', ...borderRenderDataDefinition },
{ name: 'clip', ...clipRenderDataDefinition },
]},
{ name: 'userData', type: 'uint32_t'},
{ name: 'id', type: 'uint32_t' },
{ name: 'zIndex', type: 'int16_t' },
{ name: 'commandType', type: 'uint8_t' },
{ name: '_padding', type: 'uint8_t' },
]
};
function getStructTotalSize(definition) {
switch(definition.type) {
case 'union':
case 'struct': {
let totalSize = 0;
for (const member of definition.members) {
let result = getStructTotalSize(member);
if (definition.type === 'struct') {
totalSize += result;
} else {
totalSize = Math.max(totalSize, result);
}
}
return totalSize;
}
case 'float': return 4;
case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2;
case 'int16_t': return 2;
case 'uint8_t': return 1;
case 'bool': return 1;
default: {
throw "Unimplemented C data type " + definition.type
}
}
}
function readStructAtAddress(address, definition) {
switch(definition.type) {
case 'union':
case 'struct': {
let struct = { __size: 0 };
for (const member of definition.members) {
let result = readStructAtAddress(address, member);
struct[member.name] = result;
if (definition.type === 'struct') {
struct.__size += result.__size;
address += result.__size;
} else {
struct.__size = Math.max(struct.__size, result.__size);
}
}
return struct;
}
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'int32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
case 'int16_t': return { value: memoryDataView.getInt16(address, true), __size: 2 };
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
default: {
throw "Unimplemented C data type " + definition.type
}
}
}
function getTextDimensions(text, font) {
// re-use canvas object for better performance
window.canvasContext.font = font;
let metrics = window.canvasContext.measureText(text);
return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent };
}
function createMainArena(arenaStructAddress, arenaMemoryAddress) {
let memorySize = instance.exports.Clay_MinMemorySize();
// Last arg is address to store return value
instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress);
}
async function init() {
await Promise.all(fontsById.map(f => document.fonts.load(`12px "${f}"`)));
window.htmlRoot = document.body.appendChild(document.createElement('div'));
window.canvasRoot = document.body.appendChild(document.createElement('canvas'));
window.canvasContext = window.canvasRoot.getContext("2d");
window.mousePositionXThisFrame = 0;
window.mousePositionYThisFrame = 0;
window.mouseWheelXThisFrame = 0;
window.mouseWheelYThisFrame = 0;
window.touchDown = false;
window.arrowKeyDownPressedThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
let zeroTimeout = null;
document.addEventListener("wheel", (event) => {
window.mouseWheelXThisFrame = event.deltaX * -0.1;
window.mouseWheelYThisFrame = event.deltaY * -0.1;
clearTimeout(zeroTimeout);
zeroTimeout = setTimeout(() => {
window.mouseWheelXThisFrame = 0;
window.mouseWheelYThisFrame = 0;
}, 10);
});
function handleTouch (event) {
if (event.touches.length === 1) {
window.touchDown = true;
let target = event.target;
let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.changedTouches[0].pageX + scrollLeft;
window.mousePositionYThisFrame = event.changedTouches[0].pageY + scrollTop;
}
}
document.addEventListener("touchstart", handleTouch);
document.addEventListener("touchmove", handleTouch);
document.addEventListener("touchend", () => {
window.touchDown = false;
window.mousePositionXThisFrame = 0;
window.mousePositionYThisFrame = 0;
})
document.addEventListener("mousemove", (event) => {
let target = event.target;
let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.x + scrollLeft;
window.mousePositionYThisFrame = event.y + scrollTop;
});
document.addEventListener("mousedown", (event) => {
window.mouseDown = true;
window.mouseDownThisFrame = true;
});
document.addEventListener("mouseup", (event) => {
window.mouseDown = false;
});
document.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
window.arrowKeyDownPressedThisFrame = true;
}
if (event.key === "ArrowUp") {
window.arrowKeyUpPressedThisFrame = true;
}
if (event.key === "d") {
window.dKeyPressedThisFrame = true;
}
});
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
let textDecoder = new TextDecoder("utf-8");
let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength));
let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`);
memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true);
memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true);
},
queryScrollOffsetFunction: (addressOfOffset, elementId) => {
let container = document.getElementById(elementId.toString());
if (container) {
memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
}
},
},
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch("/clay/index.wasm"), importObject
);
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
scratchSpaceAddress = instance.exports.__heap_base.value;
let clayScratchSpaceAddress = instance.exports.__heap_base.value + 1024;
heapSpaceAddress = instance.exports.__heap_base.value + 2048;
let arenaAddress = scratchSpaceAddress + 8;
window.instance = instance;
createMainArena(arenaAddress, heapSpaceAddress);
memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true);
memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true);
instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value);
instance.exports.SetScratchMemory(clayScratchSpaceAddress);
renderCommandSize = getStructTotalSize(renderCommandDefinition);
renderLoop();
}
function MemoryIsDifferent(one, two, length) {
for (let i = 0; i < length; i++) {
if (one[i] !== two[i]) {
return true;
}
}
return false;
}
function SetElementBackgroundColorAndRadius(element, cornerRadius, backgroundColor) {
element.style.backgroundColor = `rgba(${backgroundColor.r.value}, ${backgroundColor.g.value}, ${backgroundColor.b.value}, ${backgroundColor.a.value / 255})`;
if (cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = cornerRadius.topLeft.value + 'px';
}
if (cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = cornerRadius.topRight.value + 'px';
}
if (cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = cornerRadius.bottomLeft.value + 'px';
}
if (cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = cornerRadius.bottomRight.value + 'px';
}
}
function renderLoopHTML() {
let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
let previousId = 0;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let entireRenderCommandMemory = new Uint8Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let parentElement = scissorStack[scissorStack.length - 1];
let element = null;
let isMultiConfigElement = previousId === renderCommand.id.value;
if (!elementCache[renderCommand.id.value]) {
let elementType = 'div';
switch (renderCommand.commandType.value & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_TEXT:
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (renderCommand.userData.value !== 0) {
if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) {
elementType = 'a';
}
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
elementType = 'img'; break;
}
default: break;
}
element = document.createElement(elementType);
element.id = renderCommand.id.value;
if (renderCommand.commandType.value === CLAY_RENDER_COMMAND_TYPE_SCISSOR_START) {
element.style.overflow = 'hidden';
}
elementCache[renderCommand.id.value] = {
exists: true,
element: element,
previousMemoryCommand: new Uint8Array(0),
previousMemoryConfig: new Uint8Array(0),
previousMemoryText: new Uint8Array(0)
};
}
let elementData = elementCache[renderCommand.id.value];
element = elementData.element;
if (!isMultiConfigElement && Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) {
if (parentElement.nextElementIndex === 0) {
parentElement.element.insertAdjacentElement('afterbegin', element);
} else {
parentElement.element.childNodes[Math.min(parentElement.nextElementIndex - 1, parentElement.element.childNodes.length - 1)].insertAdjacentElement('afterend', element);
}
}
elementData.exists = true;
// Don't get me started. Cheaper to compare the render command memory than to update HTML elements
let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize) && !isMultiConfigElement;
if (!isMultiConfigElement) {
parentElement.nextElementIndex++;
}
previousId = renderCommand.id.value;
elementData.previousMemoryCommand = entireRenderCommandMemory;
let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0;
let offsetY = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.y : 0;
if (dirty) {
element.style.transform = `translate(${Math.round(renderCommand.boundingBox.x.value - offsetX)}px, ${Math.round(renderCommand.boundingBox.y.value - offsetY)}px)`
element.style.width = Math.round(renderCommand.boundingBox.width.value) + 'px';
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = renderCommand.renderData.rectangle;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
SetElementBackgroundColorAndRadius(element, config.cornerRadius, config.backgroundColor);
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = renderCommand.renderData.border;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
let color = config.color;
elementData.previousMemoryConfig = configMemory;
if (config.width.left.value > 0) {
element.style.borderLeft = `${config.width.left.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.right.value > 0) {
element.style.borderRight = `${config.width.right.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.top.value > 0) {
element.style.borderTop = `${config.width.top.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.bottom.value > 0) {
element.style.borderBottom = `${config.width.bottom.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
}
if (config.cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
}
if (config.cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
}
if (config.cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px';
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = renderCommand.renderData.text;
let configMemory = JSON.stringify(config);
let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
if (configMemory !== elementData.previousMemoryConfig) {
element.className = 'text';
let textColor = config.textColor;
let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR);
element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`;
element.style.fontFamily = fontsById[config.fontId.value];
element.style.fontSize = fontSize + 'px';
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory;
}
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
element.innerHTML = textDecoder.decode(stringContents);
}
elementData.previousMemoryText = stringContents;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 });
let config = renderCommand.renderData.clip;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
elementData.previousMemoryConfig = configMemory;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
scissorStack.splice(scissorStack.length - 1, 1);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value));
if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) {
element.src = textDecoder.decode(srcContents);
}
elementData.previousMemoryText = srcContents;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
default: {
console.log("Error: unhandled render command");
}
}
}
for (const key of Object.keys(elementCache)) {
if (elementCache[key].exists) {
elementCache[key].exists = false;
} else {
elementCache[key].element.remove();
delete elementCache[key];
}
}
}
function renderLoopCanvas() {
// Note: Rendering to canvas needs to be scaled up by window.devicePixelRatio in both width and height.
// e.g. if we're working on a device where devicePixelRatio is 2, we need to render
// everything at width^2 x height^2 resolution, then scale back down with css to get the correct pixel density.
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
window.canvasRoot.width = window.innerWidth * window.devicePixelRatio;
window.canvasRoot.height = window.innerHeight * window.devicePixelRatio;
window.canvasRoot.style.width = window.innerWidth + 'px';
window.canvasRoot.style.height = window.innerHeight + 'px';
let ctx = window.canvasContext;
let scale = window.devicePixelRatio;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = renderCommand.renderData.rectangle;
let color = config.backgroundColor;
ctx.beginPath();
window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
window.canvasContext.roundRect(
boundingBox.x.value * scale, // x
boundingBox.y.value * scale, // y
boundingBox.width.value * scale, // width
boundingBox.height.value * scale,
[config.cornerRadius.topLeft.value * scale, config.cornerRadius.topRight.value * scale, config.cornerRadius.bottomRight.value * scale, config.cornerRadius.bottomLeft.value * scale]) // height;
ctx.fill();
ctx.closePath();
// Handle link clicks
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = renderCommand.renderData.border;
let color = config.color;
ctx.beginPath();
ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
// Top Left Corner
if (config.cornerRadius.topLeft.value > 0) {
let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale);
ctx.stroke();
}
// Top border
if (config.width.top.value > 0) {
let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
ctx.stroke();
}
// Top Right Corner
if (config.cornerRadius.topRight.value > 0) {
let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale);
ctx.stroke();
}
// Right border
if (config.width.right.value > 0) {
let lineWidth = config.width.right.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.topRight.value - halfLineWidth) * scale);
ctx.stroke();
}
// Bottom Right Corner
if (config.cornerRadius.bottomRight.value > 0) {
let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, config.cornerRadius.bottomRight.value * scale);
ctx.stroke();
}
// Bottom Border
if (config.width.bottom.value > 0) {
let lineWidth = config.width.bottom.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.stroke();
}
// Bottom Left Corner
if (config.cornerRadius.bottomLeft.value > 0) {
let lineWidth = config.width.bottom.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale, config.cornerRadius.bottomLeft.value * scale);
ctx.stroke();
}
// Left Border
if (config.width.left.value > 0) {
let lineWidth = config.width.left.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.bottomRight.value + halfLineWidth) * scale);
ctx.stroke();
}
ctx.closePath();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = renderCommand.renderData.text;
let textContents = config.stringContents;
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale;
ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`;
let color = config.textColor;
ctx.textBaseline = 'middle';
ctx.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.fillText(textDecoder.decode(stringContents), boundingBox.x.value * scale, (boundingBox.y.value + boundingBox.height.value / 2 + 1) * scale);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
window.canvasContext.save();
window.canvasContext.beginPath();
window.canvasContext.rect(boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
window.canvasContext.clip();
window.canvasContext.closePath();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
window.canvasContext.restore();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value)));
if (!imageCache[src]) {
imageCache[src] = {
image: new Image(),
loaded: false,
}
imageCache[src].image.onload = () => imageCache[src].loaded = true;
imageCache[src].image.src = src;
} else if (imageCache[src].loaded) {
ctx.drawImage(imageCache[src].image, boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
}
}
}
function renderLoop(currentTime) {
const elapsed = currentTime - previousFrameTime;
previousFrameTime = currentTime;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
if (activeRendererIndex === 0) {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, 0, 0, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, 0, 0, window.dKeyPressedThisFrame, elapsed / 1000);
} else {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, window.arrowKeyDownPressedThisFrame, window.arrowKeyUpPressedThisFrame, window.dKeyPressedThisFrame, elapsed / 1000);
}
let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex;
switch (activeRendererIndex) {
case 0: {
renderLoopHTML();
if (rendererChanged) {
window.htmlRoot.style.display = 'block';
window.canvasRoot.style.display = 'none';
}
break;
}
case 1: {
renderLoopCanvas();
if (rendererChanged) {
window.htmlRoot.style.display = 'none';
window.canvasRoot.style.display = 'block';
}
break;
}
}
window.previousActiveRendererIndex = activeRendererIndex;
requestAnimationFrame(renderLoop);
window.mouseDownThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
window.arrowKeyDownPressedThisFrame = false;
window.dKeyPressedThisFrame = false;
}
init();
</script>
<body>
</body>
</html>

Binary file not shown.

View file

@ -42,6 +42,7 @@
position: absolute; position: absolute;
box-sizing: border-box; box-sizing: border-box;
-webkit-backface-visibility: hidden; -webkit-backface-visibility: hidden;
pointer-events: none;
} }
a { a {
@ -53,6 +54,11 @@
pointer-events: all; pointer-events: all;
white-space: pre; white-space: pre;
} }
/* TODO special exception for text selection in debug tools */
[id='2067877626'] > * {
pointer-events: none !important;
}
</style> </style>
</head> </head>
<script type="module"> <script type="module">
@ -80,6 +86,10 @@
]; ];
let elementCache = {}; let elementCache = {};
let imageCache = {}; let imageCache = {};
let dimensionsDefinition = { type: 'struct', members: [
{name: 'width', type: 'float'},
{name: 'height', type: 'float'},
]};
let colorDefinition = { type: 'struct', members: [ let colorDefinition = { type: 'struct', members: [
{name: 'r', type: 'float' }, {name: 'r', type: 'float' },
{name: 'g', type: 'float' }, {name: 'g', type: 'float' },
@ -87,12 +97,21 @@
{name: 'a', type: 'float' }, {name: 'a', type: 'float' },
]}; ]};
let stringDefinition = { type: 'struct', members: [ let stringDefinition = { type: 'struct', members: [
{name: 'isStaticallyAllocated', type: 'uint32_t'},
{name: 'length', type: 'uint32_t' }, {name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' }, {name: 'chars', type: 'uint32_t' },
]}; ]};
let borderDefinition = { type: 'struct', members: [ let stringSliceDefinition = { type: 'struct', members: [
{name: 'width', type: 'uint32_t'}, {name: 'length', type: 'uint32_t' },
{name: 'color', ...colorDefinition}, {name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderWidthDefinition = { type: 'struct', members: [
{name: 'left', type: 'uint16_t'},
{name: 'right', type: 'uint16_t'},
{name: 'top', type: 'uint16_t'},
{name: 'bottom', type: 'uint16_t'},
{name: 'betweenChildren', type: 'uint16_t'},
]}; ]};
let cornerRadiusDefinition = { type: 'struct', members: [ let cornerRadiusDefinition = { type: 'struct', members: [
{name: 'topLeft', type: 'float'}, {name: 'topLeft', type: 'float'},
@ -100,42 +119,57 @@
{name: 'bottomLeft', type: 'float'}, {name: 'bottomLeft', type: 'float'},
{name: 'bottomRight', type: 'float'}, {name: 'bottomRight', type: 'float'},
]}; ]};
let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [ 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' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'wrapMode', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: '_padding', type: 'uint16_t' },
]};
let textRenderDataDefinition = { type: 'struct', members: [
{ name: 'stringContents', ...stringSliceDefinition },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineHeight', type: 'uint16_t' },
]};
let rectangleRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
]};
let imageRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'imageData', type: 'uint32_t' },
]};
let customRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'customData', type: 'uint32_t' },
]};
let borderRenderDataDefinition = { type: 'struct', members: [
{ name: 'color', ...colorDefinition }, { name: 'color', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition }, { name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'width', ...borderWidthDefinition },
{ name: 'padding', type: 'uint16_t'}
]};
let clipRenderDataDefinition = { type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
let customHTMLDataDefinition = { type: 'struct', members: [
{ name: 'link', ...stringDefinition }, { name: 'link', ...stringDefinition },
{ name: 'cursorPointer', type: 'uint8_t' }, { name: 'cursorPointer', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: 'padding', type: 'uint16_t'}
]}; ]};
let borderConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'left', ...borderDefinition },
{ name: 'right', ...borderDefinition },
{ name: 'top', ...borderDefinition },
{ name: 'bottom', ...borderDefinition },
{ name: 'betweenChildren', ...borderDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition }
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'wrapMode', type: 'uint32_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' }
]};
let imageConfigDefinition = { name: 'image', type: 'struct', members: [
{ name: 'imageData', type: 'uint32_t' },
{ name: 'sourceDimensions', type: 'struct', members: [
{ name: 'width', type: 'float' },
{ name: 'height', type: 'float' },
]},
{ name: 'sourceURL', ...stringDefinition }
]};
let customConfigDefinition = { name: 'custom', type: 'struct', members: [
{ name: 'customData', type: 'uint32_t' },
]}
let renderCommandDefinition = { let renderCommandDefinition = {
name: 'CLay_RenderCommand', name: 'Clay_RenderCommand',
type: 'struct', type: 'struct',
members: [ members: [
{ name: 'boundingBox', type: 'struct', members: [ { name: 'boundingBox', type: 'struct', members: [
@ -144,10 +178,19 @@
{ name: 'width', type: 'float' }, { name: 'width', type: 'float' },
{ name: 'height', type: 'float' }, { name: 'height', type: 'float' },
]}, ]},
{ name: 'config', type: 'uint32_t'}, { name: 'renderData', type: 'union', members: [
{ name: 'text', ...stringDefinition }, { name: 'rectangle', ...rectangleRenderDataDefinition },
{ name: 'text', ...textRenderDataDefinition },
{ name: 'image', ...imageRenderDataDefinition },
{ name: 'custom', ...customRenderDataDefinition },
{ name: 'border', ...borderRenderDataDefinition },
{ name: 'clip', ...clipRenderDataDefinition },
]},
{ name: 'userData', type: 'uint32_t'},
{ name: 'id', type: 'uint32_t' }, { name: 'id', type: 'uint32_t' },
{ name: 'commandType', type: 'uint32_t', }, { name: 'zIndex', type: 'int16_t' },
{ name: 'commandType', type: 'uint8_t' },
{ name: '_padding', type: 'uint8_t' },
] ]
}; };
@ -168,8 +211,11 @@
} }
case 'float': return 4; case 'float': return 4;
case 'uint32_t': return 4; case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2; case 'uint16_t': return 2;
case 'int16_t': return 2;
case 'uint8_t': return 1; case 'uint8_t': return 1;
case 'bool': return 1;
default: { default: {
throw "Unimplemented C data type " + definition.type throw "Unimplemented C data type " + definition.type
} }
@ -195,8 +241,11 @@
} }
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 }; case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 }; case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'int32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 }; case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
case 'uint8_t': return { value: memoryDataView.getUint16(address, true), __size: 1 }; case 'int16_t': return { value: memoryDataView.getInt16(address, true), __size: 2 };
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
default: { default: {
throw "Unimplemented C data type " + definition.type throw "Unimplemented C data type " + definition.type
} }
@ -228,7 +277,7 @@
window.arrowKeyDownPressedThisFrame = false; window.arrowKeyDownPressedThisFrame = false;
window.arrowKeyUpPressedThisFrame = false; window.arrowKeyUpPressedThisFrame = false;
let zeroTimeout = null; let zeroTimeout = null;
addEventListener("wheel", (event) => { document.addEventListener("wheel", (event) => {
window.mouseWheelXThisFrame = event.deltaX * -0.1; window.mouseWheelXThisFrame = event.deltaX * -0.1;
window.mouseWheelYThisFrame = event.deltaY * -0.1; window.mouseWheelYThisFrame = event.deltaY * -0.1;
clearTimeout(zeroTimeout); clearTimeout(zeroTimeout);
@ -241,8 +290,17 @@
function handleTouch (event) { function handleTouch (event) {
if (event.touches.length === 1) { if (event.touches.length === 1) {
window.touchDown = true; window.touchDown = true;
window.mousePositionXThisFrame = event.changedTouches[0].pageX; let target = event.target;
window.mousePositionYThisFrame = event.changedTouches[0].pageY; let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.changedTouches[0].pageX + scrollLeft;
window.mousePositionYThisFrame = event.changedTouches[0].pageY + scrollTop;
} }
} }
@ -255,8 +313,17 @@
}) })
document.addEventListener("mousemove", (event) => { document.addEventListener("mousemove", (event) => {
window.mousePositionXThisFrame = event.x; let target = event.target;
window.mousePositionYThisFrame = event.y; let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.x + scrollLeft;
window.mousePositionYThisFrame = event.y + scrollTop;
}); });
document.addEventListener("mousedown", (event) => { document.addEventListener("mousedown", (event) => {
@ -281,29 +348,40 @@
}); });
const importObject = { const importObject = {
clay: { measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => { clay: {
let stringLength = memoryDataView.getUint32(textToMeasure, true); measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true); let stringLength = memoryDataView.getUint32(textToMeasure, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition); let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textDecoder = new TextDecoder("utf-8"); let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength)); let textDecoder = new TextDecoder("utf-8");
let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`); let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength));
memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true); let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`);
memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true); memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true);
}}, memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true);
},
queryScrollOffsetFunction: (addressOfOffset, elementId) => {
let container = document.getElementById(elementId.toString());
if (container) {
memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
}
},
},
}; };
const { instance } = await WebAssembly.instantiateStreaming( const { instance } = await WebAssembly.instantiateStreaming(
fetch("/clay/index.wasm"), importObject fetch("/clay/index.wasm"), importObject
); );
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer); memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
scratchSpaceAddress = instance.exports.__heap_base.value; scratchSpaceAddress = instance.exports.__heap_base.value;
heapSpaceAddress = instance.exports.__heap_base.value + 1024; let clayScratchSpaceAddress = instance.exports.__heap_base.value + 1024;
let arenaAddress = scratchSpaceAddress; heapSpaceAddress = instance.exports.__heap_base.value + 2048;
let arenaAddress = scratchSpaceAddress + 8;
window.instance = instance; window.instance = instance;
createMainArena(arenaAddress, heapSpaceAddress); createMainArena(arenaAddress, heapSpaceAddress);
memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true); memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true);
memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true); memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true);
instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value); instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value);
instance.exports.SetScratchMemory(clayScratchSpaceAddress);
renderCommandSize = getStructTotalSize(renderCommandDefinition); renderCommandSize = getStructTotalSize(renderCommandDefinition);
renderLoop(); renderLoop();
} }
@ -317,22 +395,43 @@
return false; return false;
} }
function SetElementBackgroundColorAndRadius(element, cornerRadius, backgroundColor) {
element.style.backgroundColor = `rgba(${backgroundColor.r.value}, ${backgroundColor.g.value}, ${backgroundColor.b.value}, ${backgroundColor.a.value / 255})`;
if (cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = cornerRadius.topLeft.value + 'px';
}
if (cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = cornerRadius.topRight.value + 'px';
}
if (cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = cornerRadius.bottomLeft.value + 'px';
}
if (cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = cornerRadius.bottomRight.value + 'px';
}
}
function renderLoopHTML() { function renderLoopHTML() {
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true); let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true); let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true); let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }]; let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
let previousId = 0;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let entireRenderCommandMemory = new Uint32Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize)); let entireRenderCommandMemory = new Uint8Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let parentElement = scissorStack[scissorStack.length - 1]; let parentElement = scissorStack[scissorStack.length - 1];
let element = null; let element = null;
let isMultiConfigElement = previousId === renderCommand.id.value;
if (!elementCache[renderCommand.id.value]) { if (!elementCache[renderCommand.id.value]) {
let elementType = 'div'; let elementType = 'div';
switch (renderCommand.commandType.value) { switch (renderCommand.commandType.value & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_TEXT:
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) { if (renderCommand.userData.value !== 0) {
elementType = 'a'; if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) {
elementType = 'a';
}
} }
break; break;
} }
@ -357,7 +456,7 @@
let elementData = elementCache[renderCommand.id.value]; let elementData = elementCache[renderCommand.id.value];
element = elementData.element; element = elementData.element;
if (Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) { if (!isMultiConfigElement && Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) {
if (parentElement.nextElementIndex === 0) { if (parentElement.nextElementIndex === 0) {
parentElement.element.insertAdjacentElement('afterbegin', element); parentElement.element.insertAdjacentElement('afterbegin', element);
} else { } else {
@ -367,8 +466,12 @@
elementData.exists = true; elementData.exists = true;
// Don't get me started. Cheaper to compare the render command memory than to update HTML elements // Don't get me started. Cheaper to compare the render command memory than to update HTML elements
let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize); let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize) && !isMultiConfigElement;
parentElement.nextElementIndex++; if (!isMultiConfigElement) {
parentElement.nextElementIndex++;
}
previousId = renderCommand.id.value;
elementData.previousMemoryCommand = entireRenderCommandMemory; elementData.previousMemoryCommand = entireRenderCommandMemory;
let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0; let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0;
@ -379,68 +482,60 @@
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px'; element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
} }
switch(renderCommand.commandType.value) { // note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): { case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); let config = renderCommand.renderData.rectangle;
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); let configMemory = JSON.stringify(config);
let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0; if (configMemory === elementData.previousMemoryConfig) {
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
break; break;
} }
if (linkContents.length > 0) {
element.href = linkContents; SetElementBackgroundColorAndRadius(element, config.cornerRadius, config.backgroundColor);
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
} }
if (linkContents.length > 0 || config.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
elementData.previousMemoryConfig = configMemory; elementData.previousMemoryConfig = configMemory;
let color = config.color;
element.style.backgroundColor = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
if (config.cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
}
if (config.cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
}
if (config.cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
}
if (config.cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px';
}
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_BORDER): { case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); let config = renderCommand.renderData.border;
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); let configMemory = JSON.stringify(config);
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) { if (configMemory === elementData.previousMemoryConfig) {
break; break;
} }
let color = config.color;
elementData.previousMemoryConfig = configMemory; elementData.previousMemoryConfig = configMemory;
if (config.left.width.value > 0) { if (config.width.left.value > 0) {
let color = config.left.color; element.style.borderLeft = `${config.width.left.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
element.style.borderLeft = `${config.left.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
} }
if (config.right.width.value > 0) { if (config.width.right.value > 0) {
let color = config.right.color; element.style.borderRight = `${config.width.right.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
element.style.borderRight = `${config.right.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
} }
if (config.top.width.value > 0) { if (config.width.top.value > 0) {
let color = config.top.color; element.style.borderTop = `${config.width.top.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
element.style.borderTop = `${config.top.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
} }
if (config.bottom.width.value > 0) { if (config.width.bottom.value > 0) {
let color = config.bottom.color; element.style.borderBottom = `${config.width.bottom.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
element.style.borderBottom = `${config.bottom.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
} }
if (config.cornerRadius.topLeft.value > 0) { if (config.cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px'; element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
@ -457,18 +552,33 @@
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_TEXT): { case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); let config = renderCommand.renderData.text;
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size)); let configMemory = JSON.stringify(config);
let textContents = renderCommand.text; let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); if (configMemory !== elementData.previousMemoryConfig) {
if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
element.className = 'text'; element.className = 'text';
let textColor = config.textColor; let textColor = config.textColor;
let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR); let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR);
element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`; element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`;
element.style.fontFamily = fontsById[config.fontId.value]; element.style.fontFamily = fontsById[config.fontId.value];
element.style.fontSize = fontSize + 'px'; element.style.fontSize = fontSize + 'px';
element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all'; if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory; elementData.previousMemoryConfig = configMemory;
} }
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) { if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
@ -479,6 +589,20 @@
} }
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 }); scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 });
let config = renderCommand.renderData.clip;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
elementData.previousMemoryConfig = configMemory;
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): { case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
@ -486,8 +610,9 @@
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); let config = renderCommand.renderData.image;
let srcContents = new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value)); let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value));
if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) { if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) {
element.src = textDecoder.decode(srcContents); element.src = textDecoder.decode(srcContents);
} }
@ -495,6 +620,9 @@
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break; case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
default: {
console.log("Error: unhandled render command");
}
} }
} }
@ -524,13 +652,15 @@
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) { for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition); let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox; let boundingBox = renderCommand.boundingBox;
switch(renderCommand.commandType.value) {
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): { case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): { case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition); let config = renderCommand.renderData.rectangle;
let color = config.color; let color = config.backgroundColor;
ctx.beginPath(); ctx.beginPath();
window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
window.canvasContext.roundRect( window.canvasContext.roundRect(
@ -542,33 +672,35 @@
ctx.fill(); ctx.fill();
ctx.closePath(); ctx.closePath();
// Handle link clicks // Handle link clicks
let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0; if (renderCommand.userData.value !== 0) {
memoryDataView.setUint32(0, renderCommand.id.value, true); let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) { let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
window.location.href = linkContents; memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
} }
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_BORDER): { case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition); let config = renderCommand.renderData.border;
let color = config.color;
ctx.beginPath(); ctx.beginPath();
ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale); ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
// Top Left Corner // Top Left Corner
if (config.cornerRadius.topLeft.value > 0) { if (config.cornerRadius.topLeft.value > 0) {
let lineWidth = config.top.width.value; let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale); ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale);
let color = config.top.color;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale); ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale);
ctx.stroke(); ctx.stroke();
} }
// Top border // Top border
if (config.top.width.value > 0) { if (config.width.top.value > 0) {
let lineWidth = config.top.width.value; let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
let color = config.top.color;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
@ -577,19 +709,17 @@
} }
// Top Right Corner // Top Right Corner
if (config.cornerRadius.topRight.value > 0) { if (config.cornerRadius.topRight.value > 0) {
let lineWidth = config.top.width.value; let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale); ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
let color = config.top.color;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale); ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale);
ctx.stroke(); ctx.stroke();
} }
// Right border // Right border
if (config.right.width.value > 0) { if (config.width.right.value > 0) {
let color = config.right.color; let lineWidth = config.width.right.value;
let lineWidth = config.right.width.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
@ -599,8 +729,7 @@
} }
// Bottom Right Corner // Bottom Right Corner
if (config.cornerRadius.bottomRight.value > 0) { if (config.cornerRadius.bottomRight.value > 0) {
let color = config.top.color; let lineWidth = config.width.top.value;
let lineWidth = config.top.width.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale); ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
@ -609,9 +738,8 @@
ctx.stroke(); ctx.stroke();
} }
// Bottom Border // Bottom Border
if (config.bottom.width.value > 0) { if (config.width.bottom.value > 0) {
let color = config.bottom.color; let lineWidth = config.width.bottom.value;
let lineWidth = config.bottom.width.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
@ -621,8 +749,7 @@
} }
// Bottom Left Corner // Bottom Left Corner
if (config.cornerRadius.bottomLeft.value > 0) { if (config.cornerRadius.bottomLeft.value > 0) {
let color = config.bottom.color; let lineWidth = config.width.bottom.value;
let lineWidth = config.bottom.width.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale); ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
@ -631,9 +758,8 @@
ctx.stroke(); ctx.stroke();
} }
// Left Border // Left Border
if (config.left.width.value > 0) { if (config.width.left.value > 0) {
let color = config.left.color; let lineWidth = config.width.left.value;
let lineWidth = config.left.width.value;
let halfLineWidth = lineWidth / 2; let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale; ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`; ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
@ -645,8 +771,8 @@
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_TEXT): { case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition); let config = renderCommand.renderData.text;
let textContents = renderCommand.text; let textContents = config.stringContents;
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value)); let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale; let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale;
ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`; ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`;
@ -669,8 +795,9 @@
break; break;
} }
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): { case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition); let config = renderCommand.renderData.image;
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value))); let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value)));
if (!imageCache[src]) { if (!imageCache[src]) {
imageCache[src] = { imageCache[src] = {
image: new Image(), image: new Image(),
@ -692,7 +819,11 @@
const elapsed = currentTime - previousFrameTime; const elapsed = currentTime - previousFrameTime;
previousFrameTime = currentTime; previousFrameTime = currentTime;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true); let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, window.arrowKeyDownPressedThisFrame, window.arrowKeyUpPressedThisFrame, window.dKeyPressedThisFrame, elapsed / 1000); if (activeRendererIndex === 0) {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, 0, 0, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, 0, 0, window.dKeyPressedThisFrame, elapsed / 1000);
} else {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, window.arrowKeyDownPressedThisFrame, window.arrowKeyUpPressedThisFrame, window.dKeyPressedThisFrame, elapsed / 1000);
}
let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex; let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex;
switch (activeRendererIndex) { switch (activeRendererIndex) {
case 0: { case 0: {

View file

@ -1,11 +1,9 @@
#define CLAY_EXTEND_CONFIG_RECTANGLE Clay_String link; bool cursorPointer; #define CLAY_IMPLEMENTATION
#define CLAY_EXTEND_CONFIG_IMAGE Clay_String sourceURL;
#define CLAY_EXTEND_CONFIG_TEXT bool disablePointerEvents;
#include "../../clay.h" #include "../../clay.h"
double windowWidth = 1024, windowHeight = 768; double windowWidth = 1024, windowHeight = 768;
float modelPageOneZRotation = 0; float modelPageOneZRotation = 0;
int ACTIVE_RENDERER_INDEX = 0; uint32_t ACTIVE_RENDERER_INDEX = 0;
const uint32_t FONT_ID_BODY_16 = 0; const uint32_t FONT_ID_BODY_16 = 0;
const uint32_t FONT_ID_TITLE_56 = 1; const uint32_t FONT_ID_TITLE_56 = 1;
@ -15,145 +13,168 @@ const uint32_t FONT_ID_TITLE_36 = 4;
const uint32_t FONT_ID_MONOSPACE_24 = 5; const uint32_t FONT_ID_MONOSPACE_24 = 5;
const Clay_Color COLOR_LIGHT = (Clay_Color) {244, 235, 230, 255}; const Clay_Color COLOR_LIGHT = (Clay_Color) {244, 235, 230, 255};
Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255}; const Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255};
Clay_Color COLOR_BUTTON_HOVER = (Clay_Color) {238, 227, 225, 255}; const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_BROWN = (Clay_Color) {61, 26, 5, 255}; const Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255};
Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255}; const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
Clay_Color COLOR_TEAL = (Clay_Color) {111, 173, 162, 255};
Clay_Color COLOR_BLUE_DARK = (Clay_Color) {2, 32, 82, 255};
// Colors for top stripe // Colors for top stripe
Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255}; const Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255};
Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; const Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255}; const Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255};
Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255}; const Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255};
Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255}; const Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255}; const Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255};
Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255}; const Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255}; const Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255};
Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255}; const Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255};
#define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y } #define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = (vector).x, .y = (vector).y }
Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }; Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 24, .textColor = {61, 26, 5, 255} };
Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 30, .textColor = COLOR_LIGHT }; Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 30, .textColor = {244, 235, 230, 255} };
typedef struct {
void* memory;
uintptr_t offset;
} Arena;
Arena frameArena = {};
typedef struct d {
Clay_String link;
bool cursorPointer;
bool disablePointerEvents;
} CustomHTMLData;
CustomHTMLData* FrameAllocateCustomData(CustomHTMLData data) {
CustomHTMLData *customData = (CustomHTMLData *)(frameArena.memory + frameArena.offset);
*customData = data;
frameArena.offset += sizeof(CustomHTMLData);
return customData;
}
Clay_String* FrameAllocateString(Clay_String string) {
Clay_String *allocated = (Clay_String *)(frameArena.memory + frameArena.offset);
*allocated = string;
frameArena.offset += sizeof(Clay_String);
return allocated;
}
void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) { void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) {
CLAY_BORDER_CONTAINER(CLAY_IDI("HeroBlob", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = {16, 16}, .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, color, 10), { CLAY(CLAY_IDI("HeroBlob", index), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) {
CLAY_IMAGE(CLAY_IDI("CheckImage", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_FIXED(32) }), CLAY_IMAGE_CONFIG(.sourceDimensions = { 128, 128 }, .sourceURL = imageURL), {}); CLAY(CLAY_IDI("CheckImage", index), { .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .aspectRatio = { 1 }, .image = { .imageData = FrameAllocateString(imageURL) } }) {}
CLAY_TEXT(CLAY_IDI("HeroBlobText", index), text, CLAY_TEXT_CONFIG(.fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color)); CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color }));
}); }
} }
void LandingPageDesktop() { void LandingPageDesktop() {
CLAY_CONTAINER(CLAY_ID("LandingPage1Desktop"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { .x = 50 }), { CLAY(CLAY_ID("LandingPage1Desktop"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY_BORDER_CONTAINER(CLAY_ID("LandingPage1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { CLAY(CLAY_ID("LandingPage1"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) {
CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("LandingPageSpacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}); }
CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), { CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
}); }
}); }
}); }
} }
void LandingPageMobile() { void LandingPageMobile() {
CLAY_CONTAINER(CLAY_ID("LandingPage1Mobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 32 }, .childGap = 32), { CLAY(CLAY_ID("LandingPage1Mobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32 }, .childGap = 32 } }) {
CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("LandingPageSpacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}); }
CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), { CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png")); LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png")); LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png")); LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png")); LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png")); LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
}); }
}); }
} }
void FeatureBlocksDesktop() { void FeatureBlocksDesktop() {
CLAY_CONTAINER(CLAY_ID("FeatureBlocksOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }), { CLAY(CLAY_ID("FeatureBlocksOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {
CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED });
CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 32}, .childGap = 8), { CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) {
CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT }));
}); }
CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
}); }
CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50, .y = 32}, .childGap = 8), { CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) {
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE }));
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
}); }
}); }
}); }
} }
void FeatureBlocksMobile() { void FeatureBlocksMobile() {
CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), { CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED ); Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED });
CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 8), { CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) {
CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), { CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT }));
}); }
CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig); CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig); CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
}); }
CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, .y = 32}, .childGap = 8), { CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) {
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE }));
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig); CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig); CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
}); }
}); }
} }
void DeclarativeSyntaxPageDesktop() { void DeclarativeSyntaxPageDesktop() {
CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY_BORDER_CONTAINER(CLAY_ID("SyntaxPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { CLAY(CLAY_ID("SyntaxPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED }}) {
CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {}
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}); }
CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
CLAY_IMAGE(CLAY_ID("SyntaxPageRightImageInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {}); CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
}); }
}); }
}); }
} }
void DeclarativeSyntaxPageMobile() { void DeclarativeSyntaxPageMobile() {
CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 16), { CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 16 } }) {
CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {}
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}); }
CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), { CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
CLAY_IMAGE(CLAY_ID("SyntaxPageRightImageInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {}); CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
}); }
}); }
} }
Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) { Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
@ -168,125 +189,143 @@ Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
void HighPerformancePageDesktop(float lerpValue) { void HighPerformancePageDesktop(float lerpValue) {
CLAY_RECTANGLE(CLAY_ID("PerformanceDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {82, 82, 32, 32}, .childGap = 64 }, .backgroundColor = COLOR_RED }) {
CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("PerformanceLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY_CONTAINER(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("PerformanceSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}); }
CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = {2, 2, 2, 2}, .color = COLOR_LIGHT } }) {
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) {
CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}); }
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) {
CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}); }
}); }
}); }
}); }
} }
void HighPerformancePageMobile(float lerpValue) { void HighPerformancePageMobile(float lerpValue) {
CLAY_RECTANGLE(CLAY_ID("PerformanceMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_RED }) {
CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("PerformanceLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY_CONTAINER(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("PerformanceSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}); }
CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), { CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = { 2, 2, 2, 2 }, .color = COLOR_LIGHT }}) {
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), { CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) {
CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}); }
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), { CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) {
CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}); }
}); }
}); }
}); }
} }
void RendererButtonActive(Clay_ElementId id, int index, Clay_String text) { void HandleRendererButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData) {
CLAY_RECTANGLE(id, CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(10)), { if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
CLAY_TEXT(CLAY_ID("RendererButtonActiveText"), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); ACTIVE_RENDERER_INDEX = (uint32_t)userData;
}); Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1);
Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0);
}
} }
void RendererButtonInactive(Clay_ElementId id, int index, Clay_String text) { void RendererButtonActive(Clay_String text) {
CLAY_BORDER_CONTAINER(id, CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { CLAY_AUTO_ID({
CLAY_RECTANGLE(CLAY_IDI("RendererButtonInactiveInner", index), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .cornerRadius = CLAY_CORNER_RADIUS(10), .cursorPointer = true), { .layout = { .sizing = {CLAY_SIZING_FIXED(300) }, .padding = CLAY_PADDING_ALL(16) },
CLAY_TEXT(CLAY_IDI("RendererButtonInactiveText", index), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); .backgroundColor = Clay_Hovered() ? COLOR_RED_HOVER : COLOR_RED,
}); .cornerRadius = CLAY_CORNER_RADIUS(10),
}); .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}
}
void RendererButtonInactive(Clay_String text, size_t rendererIndex) {
CLAY_AUTO_ID({
.layout = { .sizing = {CLAY_SIZING_FIXED(300)}, .padding = CLAY_PADDING_ALL(16) },
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
.cornerRadius = CLAY_CORNER_RADIUS(10),
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
}) {
Clay_OnHover(HandleRendererButtonInteraction, rendererIndex);
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
} }
void RendererPageDesktop() { void RendererPageDesktop() {
CLAY_CONTAINER(CLAY_ID("RendererPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), { CLAY(CLAY_ID("RendererPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY_BORDER_CONTAINER(CLAY_ID("RendererPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), { CLAY(CLAY_ID("RendererPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) {
CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("RendererSpacerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}); }
CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE }));
CLAY_CONTAINER(CLAY_ID("RendererSpacerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) } } }) {}
if (ACTIVE_RENDERER_INDEX == 0) { if (ACTIVE_RENDERER_INDEX == 0) {
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); RendererButtonActive(CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1);
} else { } else {
RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0);
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); RendererButtonActive(CLAY_STRING("Canvas Renderer"));
} }
}); }
}); }
}); }
} }
void RendererPageMobile() { void RendererPageMobile() {
CLAY_RECTANGLE(CLAY_ID("RendererMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { CLAY(CLAY_ID("RendererMobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_LIGHT }) {
CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY_CONTAINER(CLAY_ID("RendererSpacerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED)); CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}); }
CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), { CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE }));
CLAY_CONTAINER(CLAY_ID("RendererSpacerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {}); CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) }} }) {}
if (ACTIVE_RENDERER_INDEX == 0) { if (ACTIVE_RENDERER_INDEX == 0) {
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer")); RendererButtonActive(CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer")); RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1);
} else { } else {
RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer")); RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0);
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer")); RendererButtonActive(CLAY_STRING("Canvas Renderer"));
} }
}); }
}); }
} }
void DebuggerPageDesktop() { void DebuggerPageDesktop() {
CLAY_RECTANGLE(CLAY_ID("DebuggerDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED), { CLAY(CLAY_ID("DebuggerDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 82, 82, 32, 32 }, .childGap = 64 }, .backgroundColor = COLOR_RED }) {
CLAY_CONTAINER(CLAY_ID("DebuggerLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), { CLAY(CLAY_ID("DebuggerLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_ID("DebuggerTextTitle"), CLAY_STRING("Integrated Debug Tools"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Integrated Debug Tools"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY_CONTAINER(CLAY_ID("DebuggerSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {}); CLAY(CLAY_ID("DebuggerSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_IDI("DebuggerTextSubTitle", 1), CLAY_STRING("Clay includes built in \"Chrome Inspector\"-style debug tooling."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("Clay includes built in \"Chrome Inspector\"-style debug tooling."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_IDI("DebuggerTextSubTitle", 2), CLAY_STRING("View your layout hierarchy and config in real time."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT)); CLAY_TEXT(CLAY_STRING("View your layout hierarchy and config in real time."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_CONTAINER(CLAY_ID("DebuggerPageSpacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {}); CLAY(CLAY_ID("DebuggerPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_ID("DebuggerTagline"), CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG(.fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE)); CLAY_TEXT(CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}); }
CLAY_CONTAINER(CLAY_ID("DebuggerRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER}), { CLAY(CLAY_ID("DebuggerRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY_IMAGE(CLAY_ID("DebuggerPageRightImageInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 558) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1620, 1474}, .sourceURL = CLAY_STRING("/clay/images/debugger.png")), {}); CLAY(CLAY_ID("DebuggerPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .aspectRatio = { 1620.0 / 1474.0 }, .image = {.imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {}
}); }
}); }
} }
typedef struct typedef struct
@ -301,76 +340,111 @@ float animationLerpValue = -1.0f;
Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
Clay_BeginLayout(); Clay_BeginLayout();
CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {
CLAY_CONTAINER(CLAY_ID("Header"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 24, .padding = { 32 }), { CLAY(CLAY_ID("Header"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = { 32, 32 } } }) {
CLAY_TEXT(CLAY_ID("Logo"), CLAY_STRING("Clay"), &headerTextConfig); CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig);
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }), {}); CLAY(CLAY_ID("Spacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {}
if (!mobileScreen) { if (!mobileScreen) {
CLAY_RECTANGLE(CLAY_ID("LinkExamplesOuter"), &CLAY_LAYOUT_DEFAULT, CLAY_RECTANGLE_CONFIG(.link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples"), .color = {0,0,0,0}), { CLAY(CLAY_ID("LinkExamplesOuter"), { .layout = { .padding = {8, 8} } }) {
CLAY_TEXT(CLAY_ID("LinkExamplesText"), CLAY_STRING("Examples"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255})); CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({
}); .userData = FrameAllocateCustomData((CustomHTMLData) {
CLAY_RECTANGLE(CLAY_ID("LinkDocsOuter"), &CLAY_LAYOUT_DEFAULT, CLAY_RECTANGLE_CONFIG(.link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md"), .color = {0,0,0,0}), { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples")
CLAY_TEXT(CLAY_ID("LinkDocsText"), CLAY_STRING("Docs"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255})); }),
}); .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
}
CLAY(CLAY_ID("LinkDocsOuter"), { .layout = { .padding = {8, 8} } }) {
CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }),
.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })
);
}
} }
Clay_ElementId githubButtonId = CLAY_ID("HeaderButtonGithub"); CLAY_AUTO_ID({
CLAY_BORDER_CONTAINER(CLAY_ID("LinkGithubOuter"), CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), { .layout = { .padding = {16, 16, 6, 6} },
CLAY_RECTANGLE(githubButtonId, CLAY_LAYOUT(.padding = {16, 6}), CLAY_RECTANGLE_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10), .link = CLAY_STRING("https://github.com/nicbarker/clay"), .color = Clay_PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT), { .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
CLAY_TEXT(CLAY_ID("LinkGithubText"), CLAY_STRING("Github"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255})); .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
}); .cornerRadius = CLAY_CORNER_RADIUS(10),
}); .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://discord.gg/b4FTWkxdvT") }),
}); }) {
CLAY_RECTANGLE(CLAY_ID("TopBorder1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_5), {}); CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({
CLAY_RECTANGLE(CLAY_ID("TopBorder2"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_4), {}); .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }),
CLAY_RECTANGLE(CLAY_ID("TopBorder3"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_3), {}); .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
CLAY_RECTANGLE(CLAY_ID("TopBorder4"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_2), {}); }
CLAY_RECTANGLE(CLAY_ID("TopBorder5"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_1), {}); CLAY_AUTO_ID({
CLAY_RECTANGLE(CLAY_ID("ScrollContainerBackgroundRectangle"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), { .layout = { .padding = {16, 16, 6, 6} },
CLAY_SCROLL_CONTAINER(CLAY_ID("OuterScrollContainer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_SCROLL_CONFIG(.vertical = true), { .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
CLAY_BORDER_CONTAINER(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }), CLAY_BORDER_CONFIG(.betweenChildren = {2, COLOR_RED}), { .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
if (mobileScreen) { .cornerRadius = CLAY_CORNER_RADIUS(10),
LandingPageMobile(); .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }),
FeatureBlocksMobile(); }) {
DeclarativeSyntaxPageMobile(); CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({
HighPerformancePageMobile(lerpValue); .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }),
RendererPageMobile(); .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
} else { }
LandingPageDesktop(); }
FeatureBlocksDesktop(); Clay_LayoutConfig topBorderConfig = (Clay_LayoutConfig) { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(4) }};
DeclarativeSyntaxPageDesktop(); CLAY(CLAY_ID("TopBorder1"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_5 }) {}
HighPerformancePageDesktop(lerpValue); CLAY(CLAY_ID("TopBorder2"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_4 }) {}
RendererPageDesktop(); CLAY(CLAY_ID("TopBorder3"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_3 }) {}
DebuggerPageDesktop(); CLAY(CLAY_ID("TopBorder4"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_2 }) {}
} CLAY(CLAY_ID("TopBorder5"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_1 }) {}
}); CLAY(CLAY_ID("OuterScrollContainer"), {
}); .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM },
}); .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
}); .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED }
}) {
if (mobileScreen) {
LandingPageMobile();
FeatureBlocksMobile();
DeclarativeSyntaxPageMobile();
HighPerformancePageMobile(lerpValue);
RendererPageMobile();
} else {
LandingPageDesktop();
FeatureBlocksDesktop();
DeclarativeSyntaxPageDesktop();
HighPerformancePageDesktop(lerpValue);
RendererPageDesktop();
DebuggerPageDesktop();
}
}
}
if (!mobileScreen) { if (!mobileScreen && ACTIVE_RENDERER_INDEX == 1) {
Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(CLAY_ID("OuterScrollContainer")); Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_Color scrollbarColor = (Clay_Color){225, 138, 50, 120}; Clay_Color scrollbarColor = (Clay_Color){225, 138, 50, 120};
if (scrollbarData.mouseDown) { if (scrollbarData.mouseDown) {
scrollbarColor = (Clay_Color){225, 138, 50, 200}; scrollbarColor = (Clay_Color){225, 138, 50, 200};
} else if (Clay_PointerOver(CLAY_ID("ScrollBar"))) { } else if (Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
scrollbarColor = (Clay_Color){225, 138, 50, 160}; scrollbarColor = (Clay_Color){225, 138, 50, 160};
} }
float scrollHeight = scrollData.scrollContainerDimensions.height - 12; float scrollHeight = scrollData.scrollContainerDimensions.height - 12;
CLAY_FLOATING_CONTAINER(CLAY_ID("ScrollBar"), &CLAY_LAYOUT_DEFAULT, CLAY_FLOATING_CONFIG(.offset = { .x = -6, .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollHeight + 6}, .zIndex = 1, .parentId = CLAY_ID("OuterScrollContainer").id, .attachment = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP}), { CLAY(CLAY_ID("ScrollBar"), {
CLAY_RECTANGLE(CLAY_ID("ScrollBarButton"), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(10), CLAY_SIZING_FIXED((scrollHeight / scrollData.contentDimensions.height) * scrollHeight)}), CLAY_RECTANGLE_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(5), .color = scrollbarColor), {}); .floating = { .offset = { .x = -6, .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollHeight + 6}, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("OuterScrollContainer")).id, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP }, .attachTo = CLAY_ATTACH_TO_PARENT },
}); .layout = { .sizing = {CLAY_SIZING_FIXED(10), CLAY_SIZING_FIXED((scrollHeight / scrollData.contentDimensions.height) * scrollHeight)} },
.backgroundColor = scrollbarColor,
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {}
} }
return Clay_EndLayout(); return Clay_EndLayout();
} }
bool debugModeEnabled = false; bool debugModeEnabled = false;
CLAY_WASM_EXPORT("SetScratchMemory") void SetScratchMemory(void * memory) {
frameArena.memory = memory;
}
CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, bool arrowKeyDownPressedThisFrame, bool arrowKeyUpPressedThisFrame, bool dKeyPressedThisFrame, float deltaTime) { CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, bool arrowKeyDownPressedThisFrame, bool arrowKeyUpPressedThisFrame, bool dKeyPressedThisFrame, float deltaTime) {
frameArena.offset = 0;
windowWidth = width; windowWidth = width;
windowHeight = height; windowHeight = height;
Clay_SetLayoutDimensions((Clay_Dimensions) { width, height }); Clay_SetLayoutDimensions((Clay_Dimensions) { width, height });
if (deltaTime == deltaTime) { // NaN propagation can cause pain here Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_LayoutElementHashMapItem *perfPage = Clay__GetHashMapItem(Clay_GetElementId(CLAY_STRING("PerformanceOuter")).id);
// NaN propagation can cause pain here
float perfPageYOffset = perfPage->boundingBox.y + scrollContainerData.scrollPosition->y;
if (deltaTime == deltaTime && (ACTIVE_RENDERER_INDEX == 1 || (perfPageYOffset < height && perfPageYOffset + perfPage->boundingBox.height > 0))) {
animationLerpValue += deltaTime; animationLerpValue += deltaTime;
if (animationLerpValue > 1) { if (animationLerpValue > 1) {
animationLerpValue -= 2; animationLerpValue -= 2;
@ -381,30 +455,22 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
debugModeEnabled = !debugModeEnabled; debugModeEnabled = !debugModeEnabled;
Clay_SetDebugModeEnabled(debugModeEnabled); Clay_SetDebugModeEnabled(debugModeEnabled);
} }
Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1);
if (isTouchDown || isMouseDown) { Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0);
if (Clay_PointerOver(CLAY_ID("RendererSelectButtonHTML"))) {
ACTIVE_RENDERER_INDEX = 0;
} else if (Clay_PointerOver(CLAY_ID("RendererSelectButtonCanvas"))) {
ACTIVE_RENDERER_INDEX = 1;
}
}
Clay__debugViewHighlightColor = (Clay_Color) {105,210,231, 120}; Clay__debugViewHighlightColor = (Clay_Color) {105,210,231, 120};
Clay_SetPointerState((Clay_Vector2) {mousePositionX, mousePositionY}, isMouseDown); Clay_SetPointerState((Clay_Vector2) {mousePositionX, mousePositionY}, isMouseDown || isTouchDown);
if (!isMouseDown) { if (!isMouseDown) {
scrollbarData.mouseDown = false; scrollbarData.mouseDown = false;
} }
if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(CLAY_ID("ScrollBar"))) { if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("OuterScrollContainer"));
scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY }; scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY };
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
scrollbarData.mouseDown = true; scrollbarData.mouseDown = true;
} else if (scrollbarData.mouseDown) { } else if (scrollbarData.mouseDown) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("OuterScrollContainer"));
if (scrollContainerData.contentDimensions.height > 0) { if (scrollContainerData.contentDimensions.height > 0) {
Clay_Vector2 ratio = (Clay_Vector2) { Clay_Vector2 ratio = (Clay_Vector2) {
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
@ -420,12 +486,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
} }
if (arrowKeyDownPressedThisFrame) { if (arrowKeyDownPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("OuterScrollContainer"));
if (scrollContainerData.contentDimensions.height > 0) { if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50; scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
} }
} else if (arrowKeyUpPressedThisFrame) { } else if (arrowKeyUpPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(CLAY_ID("OuterScrollContainer"));
if (scrollContainerData.contentDimensions.height > 0) { if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50; scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
} }

View file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_cpp_project_example CXX)
set(CMAKE_CXX_STANDARD 20)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g")
endif()
add_executable(clay_examples_cpp_project_example main.cpp)
target_include_directories(clay_examples_cpp_project_example PUBLIC .)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()

View file

@ -0,0 +1,21 @@
#include <iostream>
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} };
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize));
Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors });
Clay_BeginLayout();
CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) {
CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
}
Clay_EndLayout();
return 0;
}

View file

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_introducing_clay_video_demo C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples
set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "5.5"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)
add_executable(clay_examples_introducing_clay_video_demo main.c)
target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC)
target_include_directories(clay_examples_introducing_clay_video_demo PUBLIC .)
target_link_libraries(clay_examples_introducing_clay_video_demo PUBLIC raylib)
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "/D CLAY_DEBUG")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
add_custom_command(
TARGET clay_examples_introducing_clay_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,55 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/raylib/clay_renderer_raylib.c"
#include "../shared-layouts/clay-video-demo.c"
// This function is new since the video was published
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
Font fonts[1];
fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR);
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
ClayVideoDemo_Data data = ClayVideoDemo_Initialize();
while (!WindowShouldClose()) {
// Run once per frame
Clay_SetLayoutDimensions((Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
});
Vector2 mousePosition = GetMousePosition();
Vector2 scrollDelta = GetMouseWheelMoveV();
Clay_SetPointerState(
(Clay_Vector2) { mousePosition.x, mousePosition.y },
IsMouseButtonDown(0)
);
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
GetFrameTime()
);
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&data);
BeginDrawing();
ClearBackground(BLACK);
Clay_Raylib_Render(renderCommands, fonts);
EndDrawing();
}
// This function is new since the video was published
Clay_Raylib_Close();
}

View file

@ -0,0 +1,3 @@
clay_playdate_example.pdx
Source/pdex.dylib
Source/pdex.elf

View file

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.27)
set(CMAKE_C_STANDARD 99)
set(ENVSDK $ENV{PLAYDATE_SDK_PATH})
if (NOT ${ENVSDK} STREQUAL "")
# Convert path from Windows
file(TO_CMAKE_PATH ${ENVSDK} SDK)
else()
execute_process(
COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config"
COMMAND head -n 1
COMMAND cut -c9-
OUTPUT_VARIABLE SDK
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
if (NOT EXISTS ${SDK})
message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH")
return()
endif()
set(CMAKE_CONFIGURATION_TYPES "Debug;Release")
set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
# Game Name Customization
set(PLAYDATE_GAME_NAME clay_playdate_example)
set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE)
project(${PLAYDATE_GAME_NAME} C ASM)
if (TOOLCHAIN STREQUAL "armgcc")
add_executable(${PLAYDATE_GAME_DEVICE} main.c)
else()
add_library(${PLAYDATE_GAME_NAME} SHARED main.c)
endif()
include(${SDK}/C_API/buildsupport/playdate_game.cmake)

View file

@ -0,0 +1,37 @@
# Playdate console example
This example uses a modified version of the document viewer application from the Clay video demo. The Playdate console has a very small black and white screen, so some of the fixed sizes and styles needed to be modified to make the application usable on the console. The selected document can be changed using up/down on the D-pad, and the selected document can be scrolled with the crank.
## Building
You need to have the (Playdate SDK)[https://play.date/dev/] installed to be able to build this example. Once it's installed you can build it by adding -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON when initialising a directory with cmake.
e.g.
```
cmake -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON cmake-build-debug
```
And then build it:
```
cmake --build cmake-build-debug
```
The pdx file will be located at examples/playdate-project-example/clay_playdate_example.pdx. You can then open it with the Playdate simulator.
## Building for the playdate device
By default building this example will produce a pdx which can only run on the Playdate simulator application. To build a pdx that can run on the Playdate hardware you need to set the toolchain to use armgcc, toolchain file to the arm.cmake provided in the playdate SDK and make sure to disable the other examples. The Playdate hardware requires threads to be disabled which is not compatible with some of the other examples.
e.g. To setup the cmake-build-release directory for device builds:
```
cmake -DTOOLCHAIN=armgcc -DCMAKE_TOOLCHAIN_FILE=/Users/mattahj/Developer/PlaydateSDK/C_API/buildsupport/arm.cmake -DCLAY_INCLUDE_ALL_EXAMPLES=OFF -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON -B cmake-build-release
```
And then build it:
```
cmake --build cmake-build-release
```

View file

@ -0,0 +1,5 @@
name=Clay Playdate Example
author=Matthew Jennings
description=A small demo of Clay running on the Playdate
bundleID=dev.mattahj.clay_example
imagePath=

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,360 @@
// This is the video demo with some adjustments so it works on the playdate
// console The playdate screen is only 400x240 pixels and it can only display
// black and white, so some fixed sizes and colours needed tweaking! The file
// menu was also removed as it does not really make sense when there is no
// pointer
//
// Note: The playdate console also does not support dynamic font sizes - fonts must be
// created at a specific size with the pdc tool - so any font size set in the clay layout
// will have no effect.
#include "pd_api.h"
#include "../../clay.h"
#include <stdlib.h>
const int FONT_ID_BODY = 0;
const int FONT_ID_BUTTON = 1;
Clay_Color COLOR_WHITE = { 255, 255, 255, 255 };
Clay_Color COLOR_BLACK = { 0, 0, 0, 255 };
void RenderHeaderButton(Clay_String text) {
CLAY_AUTO_ID({
.layout = { .padding = { 8, 8, 4, 4 } },
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
text,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE })
);
}
}
typedef struct {
Clay_String title;
Clay_String contents;
LCDBitmap* image;
} Document;
typedef struct {
Document *documents;
uint32_t length;
} DocumentArray;
#define MAX_DOCUMENTS 3
Document documentsRaw[MAX_DOCUMENTS];
DocumentArray documents = { .length = MAX_DOCUMENTS, .documents = documentsRaw };
void ClayVideoDemoPlaydate_Initialize(PlaydateAPI* pd) {
documents.documents[0] = (Document){
.title = CLAY_STRING("Squirrels"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"The Secret Life of Squirrels: Nature's Clever Acrobats\n"
"Squirrels are often overlooked creatures, dismissed as mere park "
"inhabitants or backyard nuisances. Yet, beneath their fluffy tails "
"and twitching noses lies an intricate world of cunning, agility, "
"and survival tactics that are nothing short of fascinating. As one "
"of the most common mammals in North America, squirrels have adapted "
"to a wide range of environments from bustling urban centers to "
"tranquil forests and have developed a variety of unique behaviors "
"that continue to intrigue scientists and nature enthusiasts alike.\n"
"\n"
"Master Tree Climbers\n"
"At the heart of a squirrel's skill set is its impressive ability to "
"navigate trees with ease. Whether they're darting from branch to "
"branch or leaping across wide gaps, squirrels possess an innate "
"talent for acrobatics. Their powerful hind legs, which are longer "
"than their front legs, give them remarkable jumping power. With a "
"tail that acts as a counterbalance, squirrels can leap distances of "
"up to ten times the length of their body, making them some of the "
"best aerial acrobats in the animal kingdom.\n"
"But it's not just their agility that makes them exceptional "
"climbers. Squirrels' sharp, curved claws allow them to grip tree "
"bark with precision, while the soft pads on their feet provide "
"traction on slippery surfaces. Their ability to run at high speeds "
"and scale vertical trunks with ease is a testament to the "
"evolutionary adaptations that have made them so successful in their "
"arboreal habitats.\n"
"\n"
"Food Hoarders Extraordinaire\n"
"Squirrels are often seen frantically gathering nuts, seeds, and "
"even fungi in preparation for winter. While this behavior may seem "
"like instinctual hoarding, it is actually a survival strategy that "
"has been honed over millions of years. Known as \"scatter "
"hoarding,\" squirrels store their food in a variety of hidden "
"locations, often burying it deep in the soil or stashing it in "
"hollowed-out tree trunks.\n"
"Interestingly, squirrels have an incredible memory for the "
"locations of their caches. Research has shown that they can "
"remember thousands of hiding spots, often returning to them months "
"later when food is scarce. However, they don't always recover every "
"stash some forgotten caches eventually sprout into new trees, "
"contributing to forest regeneration. This unintentional role as "
"forest gardeners highlights the ecological importance of squirrels "
"in their ecosystems.\n"
"\n"
"The Great Squirrel Debate: Urban vs. Wild\n"
"While squirrels are most commonly associated with rural or wooded "
"areas, their adaptability has allowed them to thrive in urban "
"environments as well. In cities, squirrels have become adept at "
"finding food sources in places like parks, streets, and even "
"garbage cans. However, their urban counterparts face unique "
"challenges, including traffic, predators, and the lack of natural "
"shelters. Despite these obstacles, squirrels in urban areas are "
"often observed using human infrastructure such as buildings, "
"bridges, and power lines as highways for their acrobatic "
"escapades.\n"
"There is, however, a growing concern regarding the impact of urban "
"life on squirrel populations. Pollution, deforestation, and the "
"loss of natural habitats are making it more difficult for squirrels "
"to find adequate food and shelter. As a result, conservationists "
"are focusing on creating squirrel-friendly spaces within cities, "
"with the goal of ensuring these resourceful creatures continue to "
"thrive in both rural and urban landscapes.\n"
"\n"
"A Symbol of Resilience\n"
"In many cultures, squirrels are symbols of resourcefulness, "
"adaptability, and preparation. Their ability to thrive in a variety "
"of environments while navigating challenges with agility and grace "
"serves as a reminder of the resilience inherent in nature. Whether "
"you encounter them in a quiet forest, a city park, or your own "
"backyard, squirrels are creatures that never fail to amaze with "
"their endless energy and ingenuity.\n"
"In the end, squirrels may be small, but they are mighty in their "
"ability to survive and thrive in a world that is constantly "
"changing. So next time you spot one hopping across a branch or "
"darting across your lawn, take a moment to appreciate the "
"remarkable acrobat at work a true marvel of the natural world.\n"
)
};
documents.documents[1] = (Document){
.title = CLAY_STRING("Lorem Ipsum"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim "
"ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
"aliquip ex ea commodo consequat. Duis aute irure dolor in "
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in "
"culpa qui officia deserunt mollit anim id est laborum."
)
};
documents.documents[2] = (Document){
.title = CLAY_STRING("Vacuum Instructions"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"Chapter 3: Getting Started - Unpacking and Setup\n"
"\n"
"Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In "
"this section, we will guide you through the simple steps to get "
"your vacuum up and running. Before you begin, please ensure that "
"you have all the components listed in the \"Package Contents\" "
"section on page 2.\n"
"\n"
"1. Unboxing Your Vacuum\n"
"Carefully remove the vacuum cleaner from the box. Avoid using sharp "
"objects that could damage the product. Once removed, place the unit "
"on a flat, stable surface to proceed with the setup. Inside the "
"box, you should find:\n"
"\n"
" The main vacuum unit\n"
" A telescoping extension wand\n"
" A set of specialized cleaning tools (crevice tool, upholstery "
"brush, etc.)\n"
" A reusable dust bag (if applicable)\n"
" A power cord with a 3-prong plug\n"
" A set of quick-start instructions\n"
"\n"
"2. Assembling Your Vacuum\n"
"Begin by attaching the extension wand to the main body of the "
"vacuum cleaner. Line up the connectors and twist the wand into "
"place until you hear a click. Next, select the desired cleaning "
"tool and firmly attach it to the wand's end, ensuring it is "
"securely locked in.\n"
"\n"
"For models that require a dust bag, slide the bag into the "
"compartment at the back of the vacuum, making sure it is properly "
"aligned with the internal mechanism. If your vacuum uses a bagless "
"system, ensure the dust container is correctly seated and locked in "
"place before use.\n"
"\n"
"3. Powering On\n"
"To start the vacuum, plug the power cord into a grounded electrical "
"outlet. Once plugged in, locate the power switch, usually "
"positioned on the side of the handle or body of the unit, depending "
"on your model. Press the switch to the \"On\" position, and you "
"should hear the motor begin to hum. If the vacuum does not power "
"on, check that the power cord is securely plugged in, and ensure "
"there are no blockages in the power switch.\n"
"\n"
"Note: Before first use, ensure that the vacuum filter (if your "
"model has one) is properly installed. If unsure, refer to \"Section "
"5: Maintenance\" for filter installation instructions."
)
};
}
Clay_RenderCommandArray ClayVideoDemoPlaydate_CreateLayout(int selectedDocumentIndex) {
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
Clay_BorderElementConfig contentBorders = {
.color = COLOR_BLACK,
.width = { .top = 1, .left = 1, .right = 1, .bottom = 1 }
};
// Build UI here
CLAY(CLAY_ID("OuterContainer"), {
.backgroundColor = COLOR_WHITE,
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(8),
.childGap = 4
}
}) {
// Child elements go inside braces
CLAY(CLAY_ID("HeaderBar"), {
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(30),
.width = CLAY_SIZING_GROW(0)
},
.childGap = 8,
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER }
},
}) {
// Header buttons go here
CLAY(CLAY_ID("FileButton"), {
.layout = {
.padding = { 8, 8, 4, 4 }
},
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
CLAY_STRING("File"),
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_WHITE
})
);
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY(CLAY_ID("LowerContent"), {
.layout = { .sizing = layoutExpand, .childGap = 8 },
}) {
CLAY(CLAY_ID("Sidebar"), {
.border = contentBorders,
.cornerRadius = CLAY_CORNER_RADIUS(4),
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = CLAY_PADDING_ALL(8),
.childGap = 4,
.sizing = {
.width = CLAY_SIZING_FIXED(125),
.height = CLAY_SIZING_GROW(0)
}
}
}) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW(0) },
.padding = CLAY_PADDING_ALL(8)
};
if (i == selectedDocumentIndex) {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
document.title,
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_WHITE
})
);
}
} else {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = (Clay_Color){ 0, 0, 0, Clay_Hovered() ? 120 : 0 },
.cornerRadius = CLAY_CORNER_RADIUS(4),
.border = contentBorders
}) {
CLAY_TEXT(
document.title,
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_BLACK,
})
);
}
}
}
}
CLAY(CLAY_ID("MainContent"), {
.border = contentBorders,
.cornerRadius = CLAY_CORNER_RADIUS(4),
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 8,
.padding = CLAY_PADDING_ALL(8),
.sizing = layoutExpand
}
}) {
Document selectedDocument = documents.documents[selectedDocumentIndex];
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_LEFT_TO_RIGHT,
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_BOTTOM }
}
}) {
CLAY_TEXT(
selectedDocument.title,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK })
);
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_FIXED(32),
.height = CLAY_SIZING_FIXED(30)
}
},
.image = { .imageData = selectedDocument.image, .sourceDimensions = { 32, 30 } }
}) {}
}
CLAY_TEXT(
selectedDocument.contents,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK })
);
}
}
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommandArray_Get(&renderCommands, i);
}
return renderCommands;
}

View file

@ -0,0 +1,115 @@
#include "pd_api.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/playdate/clay_renderer_playdate.c"
#include "clay-video-demo-playdate.c"
static int update(void *userdata);
#define NUM_FONTS 2
const char *fontsToLoad[NUM_FONTS] = {
"/System/Fonts/Asheville-Sans-14-Bold.pft",
"/System/Fonts/Roobert-10-Bold.pft"
};
void HandleClayErrors(Clay_ErrorData errorData) {}
struct TextUserData {
LCDFont *font[NUM_FONTS];
PlaydateAPI *pd;
};
static struct TextUserData textUserData = { .font = { NULL }, .pd = NULL };
static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
struct TextUserData *textUserData = userData;
int width = textUserData->pd->graphics->getTextWidth(
textUserData->font[config->fontId],
text.chars,
Clay_Playdate_CountUtf8Codepoints(text.chars, text.length),
kUTF8Encoding,
0
);
int height = textUserData->pd->graphics->getFontHeight(textUserData->font[config->fontId]);
return (Clay_Dimensions){
.width = (float)width,
.height = (float)height,
};
}
#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t eventArg) {
if (event == kEventInit) {
const char *err;
for (int i = 0; i < NUM_FONTS; ++i) {
textUserData.font[i] = pd->graphics->loadFont(fontsToLoad[i], &err);
if (textUserData.font[i] == NULL) {
pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontsToLoad[i], err);
}
}
textUserData.pd = pd;
pd->system->setUpdateCallback(update, pd);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(
totalMemorySize,
pd->system->realloc(NULL, totalMemorySize)
);
Clay_Initialize(
clayMemory,
(Clay_Dimensions){
(float)pd->display->getWidth(),
(float)pd->display->getHeight()
},
(Clay_ErrorHandler){HandleClayErrors}
);
Clay_SetMeasureTextFunction(PlayDate_MeasureText, &textUserData);
ClayVideoDemoPlaydate_Initialize(pd);
}
return 0;
}
int selectedDocumentIndex = 0;
#define WRAP_RANGE(x, N) ((((x) % (N)) + (N)) % (N))
static int update(void *userdata) {
PlaydateAPI *pd = userdata;
PDButtons pushedButtons;
pd->system->getButtonState(NULL, &pushedButtons, NULL);
if (pushedButtons & kButtonDown) {
selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex + 1, MAX_DOCUMENTS);
} else if (pushedButtons & kButtonUp) {
selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex - 1, MAX_DOCUMENTS);
}
pd->graphics->clear(kColorWhite);
// A bit hacky, setting the cursor on to the document view so it can be
// scrolled..
Clay_SetPointerState(
(Clay_Vector2){
.x = pd->display->getWidth() / 2.0f,
.y = pd->display->getHeight() / 2.0f
},
false
);
float crankDelta = pd->system->getCrankChange();
Clay_UpdateScrollContainers(
false,
(Clay_Vector2){ 0, -crankDelta * 0.25f },
pd->system->getElapsedTime()
);
Clay_RenderCommandArray renderCommands = ClayVideoDemoPlaydate_CreateLayout(selectedDocumentIndex);
Clay_Playdate_Render(pd, renderCommands, textUserData.font);
return 1;
}

View file

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_raylib_multi_context C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples
set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "5.5"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)
add_executable(clay_examples_raylib_multi_context main.c)
target_compile_options(clay_examples_raylib_multi_context PUBLIC)
target_include_directories(clay_examples_raylib_multi_context PUBLIC .)
target_link_libraries(clay_examples_raylib_multi_context PUBLIC raylib)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
add_custom_command(
TARGET clay_examples_raylib_multi_context POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,77 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/raylib/clay_renderer_raylib.c"
#include "../shared-layouts/clay-video-demo.c"
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
Clay_RenderCommandArray CreateLayout(Clay_Context* context, ClayVideoDemo_Data *data) {
Clay_SetCurrentContext(context);
Clay_SetDebugModeEnabled(true);
// Run once per frame
Clay_SetLayoutDimensions((Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
});
Vector2 mousePosition = GetMousePosition();
mousePosition.y -= data->yOffset;
Vector2 scrollDelta = GetMouseWheelMoveV();
Clay_SetPointerState(
(Clay_Vector2) { mousePosition.x, mousePosition.y },
IsMouseButtonDown(0)
);
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
GetFrameTime()
);
return ClayVideoDemo_CreateLayout(data);
}
int main(void) {
documents.documents = (Document[]) {
{ .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("The Secret Life of Squirrels: Nature's Clever Acrobats\n""Squirrels are often overlooked creatures, dismissed as mere park inhabitants or backyard nuisances. Yet, beneath their fluffy tails and twitching noses lies an intricate world of cunning, agility, and survival tactics that are nothing short of fascinating. As one of the most common mammals in North America, squirrels have adapted to a wide range of environments from bustling urban centers to tranquil forests and have developed a variety of unique behaviors that continue to intrigue scientists and nature enthusiasts alike.\n""\n""Master Tree Climbers\n""At the heart of a squirrel's skill set is its impressive ability to navigate trees with ease. Whether they're darting from branch to branch or leaping across wide gaps, squirrels possess an innate talent for acrobatics. Their powerful hind legs, which are longer than their front legs, give them remarkable jumping power. With a tail that acts as a counterbalance, squirrels can leap distances of up to ten times the length of their body, making them some of the best aerial acrobats in the animal kingdom.\n""But it's not just their agility that makes them exceptional climbers. Squirrels' sharp, curved claws allow them to grip tree bark with precision, while the soft pads on their feet provide traction on slippery surfaces. Their ability to run at high speeds and scale vertical trunks with ease is a testament to the evolutionary adaptations that have made them so successful in their arboreal habitats.\n""\n""Food Hoarders Extraordinaire\n""Squirrels are often seen frantically gathering nuts, seeds, and even fungi in preparation for winter. While this behavior may seem like instinctual hoarding, it is actually a survival strategy that has been honed over millions of years. Known as \"scatter hoarding,\" squirrels store their food in a variety of hidden locations, often burying it deep in the soil or stashing it in hollowed-out tree trunks.\n""Interestingly, squirrels have an incredible memory for the locations of their caches. Research has shown that they can remember thousands of hiding spots, often returning to them months later when food is scarce. However, they don't always recover every stash some forgotten caches eventually sprout into new trees, contributing to forest regeneration. This unintentional role as forest gardeners highlights the ecological importance of squirrels in their ecosystems.\n""\n""The Great Squirrel Debate: Urban vs. Wild\n""While squirrels are most commonly associated with rural or wooded areas, their adaptability has allowed them to thrive in urban environments as well. In cities, squirrels have become adept at finding food sources in places like parks, streets, and even garbage cans. However, their urban counterparts face unique challenges, including traffic, predators, and the lack of natural shelters. Despite these obstacles, squirrels in urban areas are often observed using human infrastructure such as buildings, bridges, and power lines as highways for their acrobatic escapades.\n""There is, however, a growing concern regarding the impact of urban life on squirrel populations. Pollution, deforestation, and the loss of natural habitats are making it more difficult for squirrels to find adequate food and shelter. As a result, conservationists are focusing on creating squirrel-friendly spaces within cities, with the goal of ensuring these resourceful creatures continue to thrive in both rural and urban landscapes.\n""\n""A Symbol of Resilience\n""In many cultures, squirrels are symbols of resourcefulness, adaptability, and preparation. Their ability to thrive in a variety of environments while navigating challenges with agility and grace serves as a reminder of the resilience inherent in nature. Whether you encounter them in a quiet forest, a city park, or your own backyard, squirrels are creatures that never fail to amaze with their endless energy and ingenuity.\n""In the end, squirrels may be small, but they are mighty in their ability to survive and thrive in a world that is constantly changing. So next time you spot one hopping across a branch or darting across your lawn, take a moment to appreciate the remarkable acrobat at work a true marvel of the natural world.\n") },
{ .title = CLAY_STRING("Lorem Ipsum"), .contents = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") },
{ .title = CLAY_STRING("Vacuum Instructions"), .contents = CLAY_STRING("Chapter 3: Getting Started - Unpacking and Setup\n""\n""Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In this section, we will guide you through the simple steps to get your vacuum up and running. Before you begin, please ensure that you have all the components listed in the \"Package Contents\" section on page 2.\n""\n""1. Unboxing Your Vacuum\n""Carefully remove the vacuum cleaner from the box. Avoid using sharp objects that could damage the product. Once removed, place the unit on a flat, stable surface to proceed with the setup. Inside the box, you should find:\n""\n"" The main vacuum unit\n"" A telescoping extension wand\n"" A set of specialized cleaning tools (crevice tool, upholstery brush, etc.)\n"" A reusable dust bag (if applicable)\n"" A power cord with a 3-prong plug\n"" A set of quick-start instructions\n""\n""2. Assembling Your Vacuum\n""Begin by attaching the extension wand to the main body of the vacuum cleaner. Line up the connectors and twist the wand into place until you hear a click. Next, select the desired cleaning tool and firmly attach it to the wand's end, ensuring it is securely locked in.\n""\n""For models that require a dust bag, slide the bag into the compartment at the back of the vacuum, making sure it is properly aligned with the internal mechanism. If your vacuum uses a bagless system, ensure the dust container is correctly seated and locked in place before use.\n""\n""3. Powering On\n""To start the vacuum, plug the power cord into a grounded electrical outlet. Once plugged in, locate the power switch, usually positioned on the side of the handle or body of the unit, depending on your model. Press the switch to the \"On\" position, and you should hear the motor begin to hum. If the vacuum does not power on, check that the power cord is securely plugged in, and ensure there are no blockages in the power switch.\n""\n""Note: Before first use, ensure that the vacuum filter (if your model has one) is properly installed. If unsure, refer to \"Section 5: Maintenance\" for filter installation instructions.") },
{ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") },
{ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") },
};
Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published
Font fonts[1];
fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR);
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemoryTop = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Context *clayContextTop = Clay_Initialize(clayMemoryTop, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
ClayVideoDemo_Data dataTop = ClayVideoDemo_Initialize();
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
Clay_Arena clayMemoryBottom = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Context *clayContextBottom = Clay_Initialize(clayMemoryBottom, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
ClayVideoDemo_Data dataBottom = ClayVideoDemo_Initialize();
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
while (!WindowShouldClose()) {
dataBottom.yOffset = GetScreenHeight() / 2;
Clay_RenderCommandArray renderCommandsTop = CreateLayout(clayContextTop, &dataTop);
Clay_RenderCommandArray renderCommandsBottom = CreateLayout(clayContextBottom, &dataBottom);
BeginDrawing();
ClearBackground(BLACK);
Clay_Raylib_Render(renderCommandsTop, fonts);
Clay_Raylib_Render(renderCommandsBottom, fonts);
EndDrawing();
}
Clay_Raylib_Close();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.28) cmake_minimum_required(VERSION 3.27)
project(clay_examples_raylib_sidebar_scrolling_container C) project(clay_examples_raylib_sidebar_scrolling_container C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib # Adding Raylib
include(FetchContent) include(FetchContent)
@ -10,21 +11,25 @@ set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example g
FetchContent_Declare( FetchContent_Declare(
raylib raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git" GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "master" GIT_TAG "5.5"
GIT_PROGRESS TRUE GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
) )
FetchContent_MakeAvailable(raylib) FetchContent_MakeAvailable(raylib)
set(CMAKE_C_STANDARD 99) add_executable(clay_examples_raylib_sidebar_scrolling_container main.c multi-compilation-unit.c)
add_executable(clay_examples_raylib_sidebar_scrolling_container main.c)
target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC) target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC)
target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .) target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .)
target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib) target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib)
set(CMAKE_CXX_FLAGS_RELEASE "-O3") if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
add_custom_command( add_custom_command(
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,9 @@
#include "../../clay.h"
// NOTE: This file only exists to make sure that clay works when included in multiple translation units.
void SatisfyCompiler(void) {
CLAY(CLAY_ID("SatisfyCompiler"), { }) {
CLAY_TEXT(CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24 }));
}
}

View file

@ -0,0 +1,264 @@
#include "../../clay.h"
#include <stdlib.h>
const int FONT_ID_BODY_16 = 0;
Clay_Color COLOR_WHITE = { 255, 255, 255, 255};
void RenderHeaderButton(Clay_String text) {
CLAY_AUTO_ID({
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = { 140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
void RenderDropdownMenuItem(Clay_String text) {
CLAY_AUTO_ID({.layout = { .padding = CLAY_PADDING_ALL(16)}}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
typedef struct {
Clay_String title;
Clay_String contents;
} Document;
typedef struct {
Document *documents;
uint32_t length;
} DocumentArray;
Document documentsRaw[5];
DocumentArray documents = {
.length = 5,
.documents = documentsRaw
};
typedef struct {
intptr_t offset;
intptr_t memory;
} ClayVideoDemo_Arena;
typedef struct {
int32_t selectedDocumentIndex;
float yOffset;
ClayVideoDemo_Arena frameArena;
} ClayVideoDemo_Data;
typedef struct {
int32_t requestedDocumentIndex;
int32_t* selectedDocumentIndex;
} SidebarClickData;
void HandleSidebarInteraction(
Clay_ElementId elementId,
Clay_PointerData pointerData,
intptr_t userData
) {
SidebarClickData *clickData = (SidebarClickData*)userData;
// If this button was clicked
if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) {
// Select the corresponding document
*clickData->selectedDocumentIndex = clickData->requestedDocumentIndex;
}
}
}
ClayVideoDemo_Data ClayVideoDemo_Initialize() {
documents.documents[0] = (Document){ .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("The Secret Life of Squirrels: Nature's Clever Acrobats\n""Squirrels are often overlooked creatures, dismissed as mere park inhabitants or backyard nuisances. Yet, beneath their fluffy tails and twitching noses lies an intricate world of cunning, agility, and survival tactics that are nothing short of fascinating. As one of the most common mammals in North America, squirrels have adapted to a wide range of environments from bustling urban centers to tranquil forests and have developed a variety of unique behaviors that continue to intrigue scientists and nature enthusiasts alike.\n""\n""Master Tree Climbers\n""At the heart of a squirrel's skill set is its impressive ability to navigate trees with ease. Whether they're darting from branch to branch or leaping across wide gaps, squirrels possess an innate talent for acrobatics. Their powerful hind legs, which are longer than their front legs, give them remarkable jumping power. With a tail that acts as a counterbalance, squirrels can leap distances of up to ten times the length of their body, making them some of the best aerial acrobats in the animal kingdom.\n""But it's not just their agility that makes them exceptional climbers. Squirrels' sharp, curved claws allow them to grip tree bark with precision, while the soft pads on their feet provide traction on slippery surfaces. Their ability to run at high speeds and scale vertical trunks with ease is a testament to the evolutionary adaptations that have made them so successful in their arboreal habitats.\n""\n""Food Hoarders Extraordinaire\n""Squirrels are often seen frantically gathering nuts, seeds, and even fungi in preparation for winter. While this behavior may seem like instinctual hoarding, it is actually a survival strategy that has been honed over millions of years. Known as \"scatter hoarding,\" squirrels store their food in a variety of hidden locations, often burying it deep in the soil or stashing it in hollowed-out tree trunks.\n""Interestingly, squirrels have an incredible memory for the locations of their caches. Research has shown that they can remember thousands of hiding spots, often returning to them months later when food is scarce. However, they don't always recover every stash some forgotten caches eventually sprout into new trees, contributing to forest regeneration. This unintentional role as forest gardeners highlights the ecological importance of squirrels in their ecosystems.\n""\n""The Great Squirrel Debate: Urban vs. Wild\n""While squirrels are most commonly associated with rural or wooded areas, their adaptability has allowed them to thrive in urban environments as well. In cities, squirrels have become adept at finding food sources in places like parks, streets, and even garbage cans. However, their urban counterparts face unique challenges, including traffic, predators, and the lack of natural shelters. Despite these obstacles, squirrels in urban areas are often observed using human infrastructure such as buildings, bridges, and power lines as highways for their acrobatic escapades.\n""There is, however, a growing concern regarding the impact of urban life on squirrel populations. Pollution, deforestation, and the loss of natural habitats are making it more difficult for squirrels to find adequate food and shelter. As a result, conservationists are focusing on creating squirrel-friendly spaces within cities, with the goal of ensuring these resourceful creatures continue to thrive in both rural and urban landscapes.\n""\n""A Symbol of Resilience\n""In many cultures, squirrels are symbols of resourcefulness, adaptability, and preparation. Their ability to thrive in a variety of environments while navigating challenges with agility and grace serves as a reminder of the resilience inherent in nature. Whether you encounter them in a quiet forest, a city park, or your own backyard, squirrels are creatures that never fail to amaze with their endless energy and ingenuity.\n""In the end, squirrels may be small, but they are mighty in their ability to survive and thrive in a world that is constantly changing. So next time you spot one hopping across a branch or darting across your lawn, take a moment to appreciate the remarkable acrobat at work a true marvel of the natural world.\n") };
documents.documents[1] = (Document){ .title = CLAY_STRING("Lorem Ipsum"), .contents = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") };
documents.documents[2] = (Document){ .title = CLAY_STRING("Vacuum Instructions"), .contents = CLAY_STRING("Chapter 3: Getting Started - Unpacking and Setup\n""\n""Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In this section, we will guide you through the simple steps to get your vacuum up and running. Before you begin, please ensure that you have all the components listed in the \"Package Contents\" section on page 2.\n""\n""1. Unboxing Your Vacuum\n""Carefully remove the vacuum cleaner from the box. Avoid using sharp objects that could damage the product. Once removed, place the unit on a flat, stable surface to proceed with the setup. Inside the box, you should find:\n""\n"" The main vacuum unit\n"" A telescoping extension wand\n"" A set of specialized cleaning tools (crevice tool, upholstery brush, etc.)\n"" A reusable dust bag (if applicable)\n"" A power cord with a 3-prong plug\n"" A set of quick-start instructions\n""\n""2. Assembling Your Vacuum\n""Begin by attaching the extension wand to the main body of the vacuum cleaner. Line up the connectors and twist the wand into place until you hear a click. Next, select the desired cleaning tool and firmly attach it to the wand's end, ensuring it is securely locked in.\n""\n""For models that require a dust bag, slide the bag into the compartment at the back of the vacuum, making sure it is properly aligned with the internal mechanism. If your vacuum uses a bagless system, ensure the dust container is correctly seated and locked in place before use.\n""\n""3. Powering On\n""To start the vacuum, plug the power cord into a grounded electrical outlet. Once plugged in, locate the power switch, usually positioned on the side of the handle or body of the unit, depending on your model. Press the switch to the \"On\" position, and you should hear the motor begin to hum. If the vacuum does not power on, check that the power cord is securely plugged in, and ensure there are no blockages in the power switch.\n""\n""Note: Before first use, ensure that the vacuum filter (if your model has one) is properly installed. If unsure, refer to \"Section 5: Maintenance\" for filter installation instructions.") };
documents.documents[3] = (Document){ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") };
documents.documents[4] = (Document){ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") };
ClayVideoDemo_Data data = {
.frameArena = { .memory = (intptr_t)malloc(1024) }
};
return data;
}
Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) {
data->frameArena.offset = 0;
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
Clay_Color contentBackgroundColor = { 90, 90, 90, 255 };
// Build UI here
CLAY(CLAY_ID("OuterContainer"), {
.backgroundColor = {43, 41, 51, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(16),
.childGap = 16
}
}) {
// Child elements go inside braces
CLAY(CLAY_ID("HeaderBar"), {
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(60),
.width = CLAY_SIZING_GROW(0)
},
.padding = { 16, 16, 0, 0 },
.childGap = 16,
.childAlignment = {
.y = CLAY_ALIGN_Y_CENTER
}
},
.backgroundColor = contentBackgroundColor,
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Header buttons go here
CLAY(CLAY_ID("FileButton"), {
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = {140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {
CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
bool fileMenuVisible =
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton")))
||
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu")));
if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap
CLAY(CLAY_ID("FileMenu"), {
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.attachPoints = {
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
},
.layout = {
.padding = {0, 0, 8, 8 }
}
}) {
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIXED(200)
},
},
.backgroundColor = {40, 40, 40, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Render dropdown items here
RenderDropdownMenuItem(CLAY_STRING("New"));
RenderDropdownMenuItem(CLAY_STRING("Open"));
RenderDropdownMenuItem(CLAY_STRING("Close"));
}
}
}
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY(CLAY_ID("LowerContent"), {
.layout = { .sizing = layoutExpand, .childGap = 16 }
}) {
CLAY(CLAY_ID("Sidebar"), {
.backgroundColor = contentBackgroundColor,
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = CLAY_PADDING_ALL(16),
.childGap = 8,
.sizing = {
.width = CLAY_SIZING_FIXED(250),
.height = CLAY_SIZING_GROW(0)
}
}
}) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW(0) },
.padding = CLAY_PADDING_ALL(16)
};
if (i == data->selectedDocumentIndex) {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = {120, 120, 120, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
} else {
SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset);
*clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex };
data->frameArena.offset += sizeof(SidebarClickData);
CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
Clay_OnHover(HandleSidebarInteraction, (intptr_t)clickData);
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
}
}
}
CLAY(CLAY_ID("MainContent"), {
.backgroundColor = contentBackgroundColor,
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 16,
.padding = CLAY_PADDING_ALL(16),
.sizing = layoutExpand
}
}) {
Document selectedDocument = documents.documents[data->selectedDocumentIndex];
CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
}
}
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset;
}
return renderCommands;
}

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(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(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(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 "master"
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,36 @@
cmake_minimum_required(VERSION 3.25)
project(clay_examples_termbox2_demo C)
set(CMAKE_C_STANDARD 11)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(stb)
add_executable(clay_examples_termbox2_demo main.c)
target_compile_options(clay_examples_termbox2_demo PUBLIC)
target_include_directories(clay_examples_termbox2_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR})
target_link_libraries(clay_examples_termbox2_demo PRIVATE m) # Used by stb_image.h
add_custom_command(
TARGET clay_examples_termbox2_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,789 @@
/*
Unlicense
Copyright (c) 2025 Mivirl
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/termbox2/clay_renderer_termbox2.c"
#define TB_IMPL
#include "termbox2.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
// -------------------------------------------------------------------------------------------------
// -- Internal state
// If the program should exit the main render/interaction loop
bool end_loop = false;
// If the debug tools should be displayed
bool debugMode = false;
// -------------------------------------------------------------------------------------------------
// -- Clay components
void component_text_pair(const char *key, const char *value)
{
size_t keylen = strlen(key);
size_t vallen = strlen(value);
Clay_String keytext = (Clay_String) {
.length = keylen,
.chars = key,
};
Clay_String valtext = (Clay_String) {
.length = vallen,
.chars = value,
};
Clay_TextElementConfig *textconfig =
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } });
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = {
.size.minMax = {
.min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(),
}
},
}
},
}) {
CLAY_TEXT(keytext, textconfig);
CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { }
CLAY_TEXT(valtext, textconfig);
}
}
void component_termbox_settings(void)
{
CLAY_AUTO_ID({
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.zIndex = 1,
.attachPoints = { CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_TOP },
.offset = { 0, 0 }
},
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
6 * Clay_Termbox_Cell_Width(),
6 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_ALL(1),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x00, 0x00, 0x7f }
}) {
const char *color_mode = NULL;
switch (clay_tb_color_mode) {
case TB_OUTPUT_NORMAL: {
color_mode = "TB_OUTPUT_NORMAL";
break;
}
case TB_OUTPUT_256: {
color_mode = "TB_OUTPUT_256";
break;
}
case TB_OUTPUT_216: {
color_mode = "TB_OUTPUT_216";
break;
}
case TB_OUTPUT_GRAYSCALE: {
color_mode = "TB_OUTPUT_GRAYSCALE";
break;
}
case TB_OUTPUT_TRUECOLOR: {
color_mode = "TB_OUTPUT_TRUECOLOR";
break;
}
case CLAY_TB_OUTPUT_NOCOLOR: {
color_mode = "CLAY_TB_OUTPUT_NOCOLOR";
break;
}
default: {
color_mode = "INVALID";
break;
}
}
const char *border_mode = NULL;
switch (clay_tb_border_mode) {
case CLAY_TB_BORDER_MODE_ROUND: {
border_mode = "CLAY_TB_BORDER_MODE_ROUND";
break;
}
case CLAY_TB_BORDER_MODE_MINIMUM: {
border_mode = "CLAY_TB_BORDER_MODE_MINIMUM";
break;
}
default: {
border_mode = "INVALID";
break;
}
}
const char *border_chars = NULL;
switch (clay_tb_border_chars) {
case CLAY_TB_BORDER_CHARS_ASCII: {
border_chars = "CLAY_TB_BORDER_CHARS_ASCII";
break;
}
case CLAY_TB_BORDER_CHARS_UNICODE: {
border_chars = "CLAY_TB_BORDER_CHARS_UNICODE";
break;
}
case CLAY_TB_BORDER_CHARS_BLANK: {
border_chars = "CLAY_TB_BORDER_CHARS_BLANK";
break;
}
case CLAY_TB_BORDER_CHARS_NONE: {
border_chars = "CLAY_TB_BORDER_CHARS_NONE";
break;
}
default: {
border_chars = "INVALID";
break;
}
}
const char *image_mode = NULL;
switch (clay_tb_image_mode) {
case CLAY_TB_IMAGE_MODE_PLACEHOLDER: {
image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER";
break;
}
case CLAY_TB_IMAGE_MODE_BG: {
image_mode = "CLAY_TB_IMAGE_MODE_BG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST";
break;
}
default: {
image_mode = "INVALID";
break;
}
}
const char *transparency = NULL;
if (clay_tb_transparency) {
transparency = "true";
} else {
transparency = "false";
}
CLAY_AUTO_ID({
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
}) {
component_text_pair("Color mode", color_mode);
component_text_pair("Border mode", border_mode);
component_text_pair("Border chars", border_chars);
component_text_pair("Image mode", image_mode);
component_text_pair("Transparency", transparency);
}
}
}
}
void component_color_palette(void)
{
CLAY_AUTO_ID({
.layout = {
.childGap = 16,
.padding = {
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_OUTSIDE(2),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x7f, 0x7f, 0xff }
}) {
for (int type = 0; type < 2; ++type) {
CLAY_AUTO_ID({
.layout ={
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
.childGap = Clay_Termbox_Cell_Height()
},
}) {
for (float ri = 0; ri < 4; ri += 1) {
CLAY_AUTO_ID({
.layout ={
.sizing = CLAY_SIZING_FIT(),
.childGap = Clay_Termbox_Cell_Width()
},
}) {
for (float r = ri * 0x44; r < (ri + 1) * 0x44; r += 0x22) {
CLAY_AUTO_ID({
.layout ={
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
},
}) {
for (float g = 0; g < 0xff; g += 0x22) {
CLAY_AUTO_ID({
.layout ={
.sizing = CLAY_SIZING_FIT(),
},
}) {
for (float b = 0; b < 0xff; b += 0x22) {
Clay_Color color = { r, g, b, 0x7f };
Clay_LayoutConfig layout = (Clay_LayoutConfig) {
.sizing = {
.width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
.height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
}
};
if (0 == type) {
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
} else if (1 == type) {
CLAY_AUTO_ID({
.layout = layout,
}) {
CLAY_TEXT(CLAY_STRING("#"), CLAY_TEXT_CONFIG({ .textColor = color }));
}
}
}
}
}
}
}
}
}
}
}
}
}
void component_unicode_text(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0xcc, 0xbb, 0xaa, 0xff },
.border = {
// This border should still be displayed in CLAY_TB_BORDER_MODE_ROUND mode
.width = {
0.75 * Clay_Termbox_Cell_Width(),
0.75 * Clay_Termbox_Cell_Width(),
0.75 * Clay_Termbox_Cell_Height(),
0.75 * Clay_Termbox_Cell_Height(),
},
.color = { 0x33, 0x33, 0x33, 0xff },
},
}) {
CLAY_TEXT(
CLAY_STRING("Non-ascii character tests:\n"
"\n"
"(from https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html)\n"
" Mathematics and Sciences:\n"
" ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈: ⌈x⌉ = x⌋, α ∧ ¬β = ¬(¬α β),\n"
" ⊆ ℕ₀ ⊂ , ⊥ < a ≠ b ≡ c ≤ d ≪ ⇒ (A ⇔ B),\n"
" 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n"
"\n"
" Compact font selection example text:\n"
" ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n"
" abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n"
" –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n"
" ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi<>⑀₂ἠḂӥẄɐː⍎אԱა\n"
"\n"
"(from https://blog.denisbider.com/2015/09/when-monospace-fonts-arent-unicode.html):\n"
" aeioucsz\n"
" áéíóúčšž\n"
" 台北1234\n"
" 12\n"
" アイウ1234\n"
"\n"
"(from https://stackoverflow.com/a/1644280)\n"
" ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."
),
CLAY_TEXT_CONFIG({ .textColor = { 0x11, 0x11, 0x11, 0xff } })
);
}
}
void component_keybinds(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
4 * Clay_Termbox_Cell_Width(),
4 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0x00, 0x7f, 0x7f, 0xff }
}) {
CLAY_TEXT(
CLAY_STRING(
"Termbox2 renderer test\n"
"\n"
"Keybinds:\n"
" c/C - Cycle through color modes\n"
" b/B - Cycle through border modes\n"
" h/H - Cycle through border characters\n"
" i/I - Cycle through image modes\n"
" t/T - Toggle transparency\n"
" d/D - Toggle debug mode\n"
" q/Q - Quit\n"
),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }})
);
}
}
void component_image(clay_tb_image *image, int width)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = (0 == width) ? CLAY_SIZING_GROW() : CLAY_SIZING_FIXED(width),
},
},
.image = {
.imageData = image,
},
.aspectRatio = { 512.0 / 406.0 }
}) { }
}
void component_mouse_data(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
}) {
Clay_Context* context = Clay_GetCurrentContext();
Clay_Vector2 mouse_position = context->pointerInfo.position;
Clay_LayoutConfig layout = (Clay_LayoutConfig) {
.sizing = {
.width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
.height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
}
};
float v = 255 * mouse_position.x / Clay_Termbox_Width();
v = (0 > v) ? 0 : v;
v = (255 < v) ? 255 : v;
Clay_Color color = (Clay_Color) { v, v, v, 0xff };
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
v = 255 * mouse_position.y / Clay_Termbox_Height();
v = (0 > v) ? 0 : v;
v = (255 < v) ? 255 : v;
color = (Clay_Color) { v, v, v, 0xff };
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
}
}
void component_bordered_text(void)
{
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIT(450),
.height = CLAY_SIZING_FIT(),
},
.padding = CLAY_PADDING_ALL(32)
},
.backgroundColor = { 0x24, 0x55, 0x34, 0xff },
}) {
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0xaa, 0x00, 0x00, 0xff } },
}) {
CLAY_TEXT(
CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0xaa, 0x00, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("of the border width"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("and overlap for multiple lines\nof text"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("this text\nis long enough\nto display all\n borders\naround it"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
}
}
Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image2)
{
Clay_BeginLayout();
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 64
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
CLAY_AUTO_ID({
.layout = {
.childAlignment = {
.x = CLAY_ALIGN_X_RIGHT,
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
},
}) {
component_keybinds();
component_unicode_text();
}
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 32,
.sizing = CLAY_SIZING_FIT(),
},
}) {
component_termbox_settings();
component_image(image1, 150);
component_image(image2, 0);
component_mouse_data();
component_bordered_text();
}
component_color_palette();
}
return Clay_EndLayout();
}
// -------------------------------------------------------------------------------------------------
// -- Interactive functions
void handle_clay_errors(Clay_ErrorData errorData)
{
Clay_Termbox_Close();
fprintf(stderr, "%s", errorData.errorText.chars);
exit(1);
}
/**
Process events received from termbox2 and handle interaction
*/
void handle_termbox_events(void)
{
// Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing
// If an event is already available, this doesn't wait. Will not wait due to the previous call
// to termbox_waitfor_event. Increasing the wait time reduces load without reducing
// responsiveness (but will of course prevent other code from running on this thread while it's
// waiting)
struct tb_event evt;
int ms_to_wait = 0;
int err = tb_peek_event(&evt, ms_to_wait);
switch (err) {
default:
case TB_ERR_NO_EVENT: {
break;
}
case TB_ERR_POLL: {
if (EINTR != tb_last_errno()) {
Clay_Termbox_Close();
fprintf(stderr, "Failed to read event from TTY\n");
exit(1);
}
break;
}
case TB_OK: {
switch (evt.type) {
case TB_EVENT_RESIZE: {
Clay_SetLayoutDimensions((Clay_Dimensions) {
Clay_Termbox_Width(),
Clay_Termbox_Height()
});
break;
}
case TB_EVENT_KEY: {
if (TB_KEY_CTRL_C == evt.key) {
end_loop = true;
break;
}
switch (evt.ch) {
case 'q':
case 'Q': {
end_loop = true;
break;
}
case 'd':
case 'D': {
debugMode = !debugMode;
Clay_SetDebugModeEnabled(debugMode);
break;
}
case 'c': {
int new_mode = clay_tb_color_mode - 1;
new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR;
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'C': {
int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1);
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'b': {
enum border_mode new_mode = clay_tb_border_mode - 1;
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_MINIMUM;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'B': {
enum border_mode new_mode = (clay_tb_border_mode + 1)
% (CLAY_TB_BORDER_MODE_MINIMUM + 1);
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_ROUND;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'h': {
enum border_chars new_chars = clay_tb_border_chars - 1;
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_NONE;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'H': {
enum border_chars new_chars
= (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1);
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_ASCII;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'i': {
enum image_mode new_mode = clay_tb_image_mode - 1;
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_UNICODE_FAST;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 'I': {
enum image_mode new_mode = (clay_tb_image_mode + 1)
% (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1);
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_PLACEHOLDER;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 't':
case 'T': {
Clay_Termbox_Set_Transparency(!clay_tb_transparency);
}
}
break;
}
case TB_EVENT_MOUSE: {
Clay_Vector2 mousePosition = {
(float)evt.x * Clay_Termbox_Cell_Width(),
(float)evt.y * Clay_Termbox_Cell_Height()
};
// Mouse release events may not be produced by all terminals, and will
// be sent during hover, so can't be used to detect when the mouse has
// been released
switch (evt.key) {
case TB_KEY_MOUSE_LEFT: {
Clay_SetPointerState(mousePosition, true);
break;
}
case TB_KEY_MOUSE_RIGHT: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_MIDDLE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_RELEASE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
break;
}
}
}
int main(void)
{
clay_tb_image shark_image1 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg");
clay_tb_image shark_image2 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg");
if (NULL == shark_image1.pixel_data) { exit(1); }
if (NULL == shark_image2.pixel_data) { exit(1); }
int num_elements = 3 * 8192;
Clay_SetMaxElementCount(num_elements);
uint64_t size = Clay_MinMemorySize();
void *memory = malloc(size);
if (NULL == memory) { exit(1); }
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory);
Clay_Termbox_Initialize(
TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false);
Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() },
(Clay_ErrorHandler) { handle_clay_errors, NULL });
Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL);
// Initial render before waiting for events
Clay_RenderCommandArray commands = CreateLayout(&shark_image1, &shark_image2);
Clay_Termbox_Render(commands);
tb_present();
while (!end_loop) {
// Block until event is available. Optional, but reduces load since this demo is purely
// synchronous to user input.
Clay_Termbox_Waitfor_Event();
handle_termbox_events();
commands = CreateLayout(&shark_image1, &shark_image2);
tb_clear();
Clay_Termbox_Render(commands);
tb_present();
}
Clay_Termbox_Close();
Clay_Termbox_Image_Free(&shark_image1);
Clay_Termbox_Image_Free(&shark_image2);
free(memory);
return 0;
}

View file

@ -0,0 +1,72 @@
# Termbox2 renderer demo
Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2)
This demo shows a color palette and a few different components. It allows
changing configuration settings for colors, border size rounding mode,
characters used for borders, and transparency.
```
Keybinds:
c/C - Cycle through color modes
b/B - Cycle through border modes
h/H - Cycle through border characters
i/I - Cycle through image modes
t/T - Toggle transparency
d/D - Toggle debug mode
q/Q - Quit
```
Configuration can be also be overriden by environment variables:
- `CLAY_TB_COLOR_MODE`
- `NORMAL`
- `256`
- `216`
- `GRAYSCALE`
- `TRUECOLOR`
- `NOCOLOR`
- `CLAY_TB_BORDER_CHARS`
- `DEFAULT`
- `ASCII`
- `UNICODE`
- `NONE`
- `CLAY_TB_IMAGE_MODE`
- `DEFAULT`
- `PLACEHOLDER`
- `BG`
- `ASCII_FG`
- `ASCII`
- `UNICODE`
- `ASCII_FG_FAST`
- `ASCII_FAST`
- `UNICODE_FAST`
- `CLAY_TB_TRANSPARENCY`
- `1`
- `0`
- `CLAY_TB_CELL_PIXELS`
- `widthxheight`
## Building
Build the binary with cmake
```sh
mkdir build
cd build
cmake ..
make
```
Then run the executable:
```sh
./clay_examples_termbox2_demo
```
## Attributions
Resources used:
- `512px-Shark_antwerp_zoo.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Shark_antwerp_zoo.jpg>
- License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en)
- No changes made

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.25)
project(clay_examples_termbox2_image_demo C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_C_FLAGS_RELEASE "-O3")
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(stb)
add_executable(clay_examples_termbox2_image_demo main.c)
target_compile_options(clay_examples_termbox2_image_demo PUBLIC)
target_include_directories(clay_examples_termbox2_image_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR})
target_link_libraries(clay_examples_termbox2_image_demo PRIVATE m) # Used by stb_image.h
add_custom_command(
TARGET clay_examples_termbox2_image_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,707 @@
/*
Unlicense
Copyright (c) 2025 Mivirl
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/termbox2/clay_renderer_termbox2.c"
#define TB_IMPL
#include "termbox2.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
// -------------------------------------------------------------------------------------------------
// -- Data structures
struct img_group {
clay_tb_image thumbnail;
clay_tb_image image;
clay_tb_image image_1;
clay_tb_image image_2;
int width;
int height;
};
typedef struct img_group img_group;
// -------------------------------------------------------------------------------------------------
// -- Internal state
// If the program should exit the main render/interaction loop
bool end_loop = false;
// If the debug tools should be displayed
bool debugMode = false;
// -------------------------------------------------------------------------------------------------
// -- Internal utility functions
img_group img_group_load(const char *filename)
{
img_group rv;
rv.thumbnail = Clay_Termbox_Image_Load_File(filename);
rv.image = Clay_Termbox_Image_Load_File(filename);
rv.image_1 = Clay_Termbox_Image_Load_File(filename);
rv.image_2 = Clay_Termbox_Image_Load_File(filename);
if (NULL == rv.thumbnail.pixel_data
|| NULL == rv.image.pixel_data
|| NULL == rv.image_1.pixel_data
|| NULL == rv.image_2.pixel_data) {
exit(1);
}
rv.width = rv.image.pixel_width;
rv.height = rv.image.pixel_height;
return rv;
}
void img_group_free(img_group *img)
{
Clay_Termbox_Image_Free(&img->thumbnail);
Clay_Termbox_Image_Free(&img->image);
Clay_Termbox_Image_Free(&img->image_1);
Clay_Termbox_Image_Free(&img->image_2);
}
// -------------------------------------------------------------------------------------------------
// -- Clay components
void component_text_pair(const char *key, const char *value)
{
size_t keylen = strlen(key);
size_t vallen = strlen(value);
Clay_String keytext = (Clay_String) {
.length = keylen,
.chars = key,
};
Clay_String valtext = (Clay_String) {
.length = vallen,
.chars = value,
};
Clay_TextElementConfig *textconfig =
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } });
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = {
.size.minMax = {
.min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(),
}
},
}
},
}) {
CLAY_TEXT(keytext, textconfig);
CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { }
CLAY_TEXT(valtext, textconfig);
}
}
void component_termbox_settings(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
6 * Clay_Termbox_Cell_Width(),
6 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_ALL(1),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x00, 0x00, 0x7f }
}) {
const char *color_mode = NULL;
switch (clay_tb_color_mode) {
case TB_OUTPUT_NORMAL: {
color_mode = "TB_OUTPUT_NORMAL";
break;
}
case TB_OUTPUT_256: {
color_mode = "TB_OUTPUT_256";
break;
}
case TB_OUTPUT_216: {
color_mode = "TB_OUTPUT_216";
break;
}
case TB_OUTPUT_GRAYSCALE: {
color_mode = "TB_OUTPUT_GRAYSCALE";
break;
}
case TB_OUTPUT_TRUECOLOR: {
color_mode = "TB_OUTPUT_TRUECOLOR";
break;
}
case CLAY_TB_OUTPUT_NOCOLOR: {
color_mode = "CLAY_TB_OUTPUT_NOCOLOR";
break;
}
default: {
color_mode = "INVALID";
break;
}
}
const char *border_mode = NULL;
switch (clay_tb_border_mode) {
case CLAY_TB_BORDER_MODE_ROUND: {
border_mode = "CLAY_TB_BORDER_MODE_ROUND";
break;
}
case CLAY_TB_BORDER_MODE_MINIMUM: {
border_mode = "CLAY_TB_BORDER_MODE_MINIMUM";
break;
}
default: {
border_mode = "INVALID";
break;
}
}
const char *border_chars = NULL;
switch (clay_tb_border_chars) {
case CLAY_TB_BORDER_CHARS_ASCII: {
border_chars = "CLAY_TB_BORDER_CHARS_ASCII";
break;
}
case CLAY_TB_BORDER_CHARS_UNICODE: {
border_chars = "CLAY_TB_BORDER_CHARS_UNICODE";
break;
}
case CLAY_TB_BORDER_CHARS_BLANK: {
border_chars = "CLAY_TB_BORDER_CHARS_BLANK";
break;
}
case CLAY_TB_BORDER_CHARS_NONE: {
border_chars = "CLAY_TB_BORDER_CHARS_NONE";
break;
}
default: {
border_chars = "INVALID";
break;
}
}
const char *image_mode = NULL;
switch (clay_tb_image_mode) {
case CLAY_TB_IMAGE_MODE_PLACEHOLDER: {
image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER";
break;
}
case CLAY_TB_IMAGE_MODE_BG: {
image_mode = "CLAY_TB_IMAGE_MODE_BG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST";
break;
}
default: {
image_mode = "INVALID";
break;
}
}
const char *transparency = NULL;
if (clay_tb_transparency) {
transparency = "true";
} else {
transparency = "false";
}
CLAY_AUTO_ID({
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
}) {
component_text_pair("Color mode", color_mode);
component_text_pair("Border mode", border_mode);
component_text_pair("Border chars", border_chars);
component_text_pair("Image mode", image_mode);
component_text_pair("Transparency", transparency);
}
}
}
void component_keybinds(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
4 * Clay_Termbox_Cell_Width(),
4 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0x00, 0x7f, 0x7f, 0xff }
}) {
CLAY_TEXT(
CLAY_STRING(
"Termbox2 renderer test\n"
"\n"
"Keybinds:\n"
" up/down arrows - Change selected image\n"
" c/C - Cycle through color modes\n"
" b/B - Cycle through border modes\n"
" h/H - Cycle through border characters\n"
" i/I - Cycle through image modes\n"
" t/T - Toggle transparency\n"
" d/D - Toggle debug mode\n"
" q/Q - Quit\n"
),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }})
);
}
}
void component_image(img_group *img_pair)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 1 * Clay_Termbox_Cell_Height()
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.image = {
.imageData = &img_pair->image,
},
.aspectRatio = { (float)img_pair->width / img_pair->height }
}) { }
component_keybinds();
}
}
void component_image_small(img_group **img_pairs, int count, int selected_index)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.25),
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 20,
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
},
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.7),
},
},
.image = {
.imageData = &img_pairs[selected_index]->image_1,
},
.aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height }
}) { }
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.image = {
.imageData = &img_pairs[selected_index]->image_2,
},
.aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height }
}) { }
component_termbox_settings();
}
}
void component_thumbnails(img_group **img_pairs, int count, int selected_index)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.1),
.height = CLAY_SIZING_GROW()
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 20
},
.backgroundColor = { 0x42, 0x42, 0x42, 0xff }
}) {
for (int i = 0; i < count; ++i) {
Clay_BorderElementConfig border;
if (i == selected_index) {
border = (Clay_BorderElementConfig) {
.width =CLAY_BORDER_OUTSIDE(10),
.color = { 0x00, 0x30, 0xc0, 0x8f }
};
} else {
border = (Clay_BorderElementConfig) {
.width = CLAY_BORDER_OUTSIDE(0),
};
}
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.border = border,
.image = {
.imageData = &img_pairs[i]->thumbnail,
},
.aspectRatio = { (float)img_pairs[i]->width / img_pairs[i]->height }
}) { }
}
}
}
int selected_thumbnail = 0;
const int thumbnail_count = 5;
Clay_RenderCommandArray CreateLayout(struct img_group **imgs)
{
Clay_BeginLayout();
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.childAlignment = {
.x = CLAY_ALIGN_X_LEFT,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 64
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
component_thumbnails(imgs, thumbnail_count, selected_thumbnail);
component_image_small(imgs, thumbnail_count, selected_thumbnail);
component_image(imgs[selected_thumbnail]);
}
return Clay_EndLayout();
}
// -------------------------------------------------------------------------------------------------
// -- Interactive functions
void handle_clay_errors(Clay_ErrorData errorData)
{
Clay_Termbox_Close();
fprintf(stderr, "%s", errorData.errorText.chars);
exit(1);
}
/**
Process events received from termbox2 and handle interaction
*/
void handle_termbox_events(void)
{
// Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing
// If an event is already available, this doesn't wait. Will not wait due to the previous call
// to termbox_waitfor_event. Increasing the wait time reduces load without reducing
// responsiveness (but will of course prevent other code from running on this thread while it's
// waiting)
struct tb_event evt;
int ms_to_wait = 0;
int err = tb_peek_event(&evt, ms_to_wait);
switch (err) {
default:
case TB_ERR_NO_EVENT: {
break;
}
case TB_ERR_POLL: {
if (EINTR != tb_last_errno()) {
Clay_Termbox_Close();
fprintf(stderr, "Failed to read event from TTY\n");
exit(1);
}
break;
}
case TB_OK: {
switch (evt.type) {
case TB_EVENT_RESIZE: {
Clay_SetLayoutDimensions((Clay_Dimensions) {
Clay_Termbox_Width(),
Clay_Termbox_Height()
});
break;
}
case TB_EVENT_KEY: {
if (TB_KEY_CTRL_C == evt.key) {
end_loop = true;
break;
}
if (0 != evt.ch) {
switch (evt.ch) {
case 'q':
case 'Q': {
end_loop = true;
break;
}
case 'd':
case 'D': {
debugMode = !debugMode;
Clay_SetDebugModeEnabled(debugMode);
break;
}
case 'c': {
int new_mode = clay_tb_color_mode - 1;
new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR;
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'C': {
int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1);
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'b': {
enum border_mode new_mode = clay_tb_border_mode - 1;
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_MINIMUM;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'B': {
enum border_mode new_mode = (clay_tb_border_mode + 1)
% (CLAY_TB_BORDER_MODE_MINIMUM + 1);
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_ROUND;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'h': {
enum border_chars new_chars = clay_tb_border_chars - 1;
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_NONE;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'H': {
enum border_chars new_chars
= (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1);
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_ASCII;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'i': {
enum image_mode new_mode = clay_tb_image_mode - 1;
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_UNICODE_FAST;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 'I': {
enum image_mode new_mode = (clay_tb_image_mode + 1)
% (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1);
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_PLACEHOLDER;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 't':
case 'T': {
Clay_Termbox_Set_Transparency(!clay_tb_transparency);
}
}
} else if (0 != evt.key) {
switch (evt.key) {
case TB_KEY_ARROW_UP: {
selected_thumbnail = (selected_thumbnail > 0) ? selected_thumbnail - 1 : 0;
break;
}
case TB_KEY_ARROW_DOWN: {
selected_thumbnail = (selected_thumbnail < thumbnail_count - 1) ? selected_thumbnail + 1 : thumbnail_count - 1;
break;
}
}
}
break;
}
case TB_EVENT_MOUSE: {
Clay_Vector2 mousePosition = {
(float)evt.x * Clay_Termbox_Cell_Width(),
(float)evt.y * Clay_Termbox_Cell_Height()
};
// Mouse release events may not be produced by all terminals, and will
// be sent during hover, so can't be used to detect when the mouse has
// been released
switch (evt.key) {
case TB_KEY_MOUSE_LEFT: {
Clay_SetPointerState(mousePosition, true);
break;
}
case TB_KEY_MOUSE_RIGHT: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_MIDDLE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_RELEASE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
break;
}
}
}
int main(void)
{
img_group *imgs[thumbnail_count];
img_group img_shark = img_group_load("resources/512px-Shark_antwerp_zoo.jpeg");
img_group img_castle = img_group_load("resources/512px-Balmoral_Castle_30_July_2011.jpeg");
img_group img_dog = img_group_load("resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg");
img_group img_rosa = img_group_load("resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg");
img_group img_vorderer = img_group_load("resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg");
imgs[0] = &img_shark;
imgs[1] = &img_castle;
imgs[2] = &img_dog;
imgs[3] = &img_rosa;
imgs[4] = &img_vorderer;
int num_elements = 3 * 8192;
Clay_SetMaxElementCount(num_elements);
uint64_t size = Clay_MinMemorySize();
void *memory = malloc(size);
if (NULL == memory) { exit(1); }
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory);
Clay_Termbox_Initialize(
TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false);
Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() },
(Clay_ErrorHandler) { handle_clay_errors, NULL });
Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL);
// Initial render before waiting for events
Clay_RenderCommandArray commands = CreateLayout(imgs);
Clay_Termbox_Render(commands);
tb_present();
while (!end_loop) {
// Block until event is available. Optional, but reduces load since this demo is purely
// synchronous to user input.
Clay_Termbox_Waitfor_Event();
handle_termbox_events();
commands = CreateLayout(imgs);
Clay_Termbox_Render(commands);
tb_present();
}
Clay_Termbox_Close();
img_group_free(&img_shark);
img_group_free(&img_castle);
img_group_free(&img_dog);
img_group_free(&img_rosa);
img_group_free(&img_vorderer);
free(memory);
return 0;
}

View file

@ -0,0 +1,88 @@
# Termbox2 renderer demo
Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2)
This demo shows a color palette and a few different components. It allows
changing configuration settings for colors, border size rounding mode,
characters used for borders, and transparency.
```
Keybinds:
c/C - Cycle through color modes
b/B - Cycle through border modes
h/H - Cycle through border characters
i/I - Cycle through image modes
t/T - Toggle transparency
d/D - Toggle debug mode
q/Q - Quit
```
Configuration can be also be overriden by environment variables:
- `CLAY_TB_COLOR_MODE`
- `NORMAL`
- `256`
- `216`
- `GRAYSCALE`
- `TRUECOLOR`
- `NOCOLOR`
- `CLAY_TB_BORDER_CHARS`
- `DEFAULT`
- `ASCII`
- `UNICODE`
- `NONE`
- `CLAY_TB_IMAGE_MODE`
- `DEFAULT`
- `PLACEHOLDER`
- `BG`
- `ASCII_FG`
- `ASCII`
- `UNICODE`
- `ASCII_FG_FAST`
- `ASCII_FAST`
- `UNICODE_FAST`
- `CLAY_TB_TRANSPARENCY`
- `1`
- `0`
- `CLAY_TB_CELL_PIXELS`
- `widthxheight`
## Building
Build the binary with cmake
```sh
mkdir build
cd build
cmake ..
make
```
Then run the executable:
```sh
./clay_examples_termbox2_demo
```
## Attributions
Resources used:
- `512px-Shark_antwerp_zoo.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Shark_antwerp_zoo.jpg>
- License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/)
- No changes made
- `512px-Balmoral_Castle_30_July_2011.jpg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Balmoral_Castle_30_July_2011.jpg>
- License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/)
- No changes made
- `512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Sch%C3%A4ferhund_(Folder_(IV)_22.JPG>
- License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/)
- No changes made
- `512px-Rosa_Cubana_2018-09-21_1610.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Rosa_Cubana_2018-09-21_1610.jpg>
- License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
- No changes made
- `512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Vorderer_Graben_10_Bamberg_20190830_001.jpg>
- License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
- No changes made

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View file

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_terminal C)
set(CMAKE_C_STANDARD 99)
add_executable(clay_examples_terminal main.c)
target_compile_options(clay_examples_terminal PUBLIC)
target_include_directories(clay_examples_terminal PUBLIC .)
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_link_libraries(clay_examples_terminal INTERFACE m)
endif()
target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY})
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

View file

@ -0,0 +1,39 @@
// Must be defined in one file, _before_ #include "clay.h"
#define CLAY_IMPLEMENTATION
#include <unistd.h>
#include "../../clay.h"
#include "../../renderers/terminal/clay_renderer_terminal_ansi.c"
#include "../shared-layouts/clay-video-demo.c"
const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main() {
const int width = 145;
const int height = 41;
int columnWidth = 16;
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(arena,
(Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth},
(Clay_ErrorHandler) {HandleClayErrors});
// Tell clay how to measure text
Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth);
ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize();
while (true) {
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
Clay_Terminal_Render(renderCommands, width, height, columnWidth);
fflush(stdout);
sleep(1);
}
}

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

@ -1,7 +0,0 @@
$TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return $DEFAULT_VALUE$;
}

View file

@ -1,5 +0,0 @@
void $NAME$_Add($NAME$ *array, $TYPE$ item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
}
}

View file

@ -1,10 +0,0 @@
typedef struct
{
uint32_t capacity;
uint32_t length;
$TYPE$ *internalArray;
} $NAME$;
$NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return ($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__ALIGNMENT($TYPE$), arena)};
}

Some files were not shown because too many files have changed in this diff Show more