├── .dockerignore ├── .github ├── FUNDING.yml └── workflows │ └── cmake-multi-platform.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE.md ├── README.md ├── bindings ├── cpp │ └── README.md ├── csharp │ └── README ├── odin │ ├── .gitignore │ ├── README.md │ ├── build-clay-lib.sh │ ├── clay-odin │ │ ├── clay.odin │ │ ├── linux │ │ │ └── clay.a │ │ ├── macos-arm64 │ │ │ └── clay.a │ │ ├── macos │ │ │ └── clay.a │ │ ├── wasm │ │ │ └── clay.o │ │ └── windows │ │ │ └── clay.lib │ ├── examples │ │ └── clay-official-website │ │ │ ├── clay-official-website.odin │ │ │ ├── clay_renderer_raylib.odin │ │ │ └── resources │ │ │ ├── Calistoga-Regular.ttf │ │ │ ├── Quicksand-Semibold.ttf │ │ │ ├── check_1.png │ │ │ ├── check_2.png │ │ │ ├── check_3.png │ │ │ ├── check_4.png │ │ │ ├── check_5.png │ │ │ └── declarative.png │ ├── odinfmt.json │ └── ols.json ├── rust │ └── README └── zig │ └── README ├── clay.h ├── cmake └── FindCairo.cmake ├── examples ├── SDL2-video-demo │ ├── CMakeLists.txt │ ├── main.c │ └── resources │ │ ├── Roboto-Regular.ttf │ │ └── sample.png ├── SDL3-simple-demo │ ├── CMakeLists.txt │ ├── main.c │ └── resources │ │ ├── Roboto-Regular.ttf │ │ └── sample.png ├── cairo-pdf-rendering │ ├── CMakeLists.txt │ ├── main.c │ └── resources │ │ └── check.png ├── clay-official-website │ ├── CMakeLists.txt │ ├── build.sh │ ├── build │ │ └── clay │ │ │ ├── fonts │ │ │ ├── Calistoga-Regular.ttf │ │ │ └── Quicksand-Semibold.ttf │ │ │ ├── images │ │ │ ├── check_1.png │ │ │ ├── check_2.png │ │ │ ├── check_3.png │ │ │ ├── check_4.png │ │ │ ├── check_5.png │ │ │ ├── debugger.png │ │ │ ├── declarative.png │ │ │ └── renderer.png │ │ │ ├── index.html │ │ │ └── index.wasm │ ├── fonts │ │ ├── Calistoga-Regular.ttf │ │ └── Quicksand-Semibold.ttf │ ├── images │ │ ├── check_1.png │ │ ├── check_2.png │ │ ├── check_3.png │ │ ├── check_4.png │ │ ├── check_5.png │ │ ├── debugger.png │ │ ├── declarative.png │ │ └── renderer.png │ ├── index.html │ └── main.c ├── cpp-project-example │ ├── CMakeLists.txt │ └── main.cpp ├── introducing-clay-video-demo │ ├── CMakeLists.txt │ ├── main.c │ └── resources │ │ └── Roboto-Regular.ttf ├── playdate-project-example │ ├── .gitignore │ ├── CmakeLists.txt │ ├── README.md │ ├── Source │ │ ├── pdxinfo │ │ └── star.png │ ├── clay-video-demo-playdate.c │ └── main.c ├── raylib-multi-context │ ├── CMakeLists.txt │ ├── main.c │ └── resources │ │ ├── Roboto-Regular.ttf │ │ ├── RobotoMono-Medium.ttf │ │ └── profile-picture.png ├── raylib-sidebar-scrolling-container │ ├── CMakeLists.txt │ ├── main.c │ ├── multi-compilation-unit.c │ └── resources │ │ ├── Roboto-Regular.ttf │ │ ├── RobotoMono-Medium.ttf │ │ └── profile-picture.png ├── shared-layouts │ └── clay-video-demo.c ├── sokol-corner-radius │ ├── CMakeLists.txt │ └── main.c ├── sokol-video-demo │ ├── CMakeLists.txt │ ├── main.c │ ├── resources │ │ └── Roboto-Regular.ttf │ └── sokol.c ├── terminal-example │ ├── CMakeLists.txt │ └── main.c └── win32_gdi │ ├── CMakeLists.txt │ ├── build.ps1 │ ├── main.c │ └── resources │ └── Roboto-Regular.ttf ├── renderers ├── SDL2 │ ├── README │ └── clay_renderer_SDL2.c ├── SDL3 │ └── clay_renderer_SDL3.c ├── cairo │ └── clay_renderer_cairo.c ├── playdate │ └── clay_renderer_playdate.c ├── raylib │ ├── clay_renderer_raylib.c │ ├── raylib.h │ └── raymath.h ├── sokol │ └── sokol_clay.h ├── terminal │ └── clay_renderer_terminal_ansi.c ├── web │ ├── build-wasm.sh │ ├── canvas2d │ │ └── clay-canvas2d-renderer.html │ ├── clay.wasm │ └── html │ │ └── clay-html-renderer.html └── win32_gdi │ ├── README.md │ └── clay_renderer_gdi.c └── tests ├── docker-compose.yml ├── gcc └── 9.4 │ └── Dockerfile └── run-tests.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug/ 2 | cmake-build-release/ 3 | .DS_Store 4 | .idea/ 5 | build/ 6 | node_modules/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nicbarker] 2 | -------------------------------------------------------------------------------- /.github/workflows/cmake-multi-platform.yml: -------------------------------------------------------------------------------- 1 | name: CMake on multiple platforms 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | # 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. 15 | fail-fast: false 16 | 17 | # Set up a matrix to run the following 3 configurations: 18 | # 1. 19 | # 2. 20 | # 3. 21 | # 22 | # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. 23 | matrix: 24 | os: [ubuntu-latest, windows-latest] 25 | build_type: [Release] 26 | c_compiler: [gcc, clang, cl] 27 | include: 28 | - os: windows-latest 29 | c_compiler: cl 30 | cpp_compiler: cl 31 | - os: ubuntu-latest 32 | c_compiler: gcc 33 | cpp_compiler: g++ 34 | - os: ubuntu-latest 35 | c_compiler: clang 36 | cpp_compiler: clang++ 37 | exclude: 38 | - os: windows-latest 39 | c_compiler: gcc 40 | - os: windows-latest 41 | c_compiler: clang 42 | - os: ubuntu-latest 43 | c_compiler: cl 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - name: Set reusable strings 49 | # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. 50 | id: strings 51 | shell: bash 52 | run: | 53 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" 54 | 55 | - name: Cache 56 | uses: actions/cache@v4.2.0 57 | with: 58 | # A list of files, directories, and wildcard patterns to cache and restore 59 | path: "/home/runner/work/clay/clay/build/_deps" 60 | # An explicit key for restoring and saving the cache 61 | key: "_deps" 62 | 63 | - name: Install Dependencies 64 | if: runner.os == 'Linux' 65 | run: | 66 | DEBIAN_FRONTEND=noninteractive sudo apt-get update -y 67 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git 68 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libwayland-dev 69 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y pkg-config 70 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libxkbcommon-dev 71 | DEBIAN_FRONTEND=noninteractive sudo apt-get install -y xorg-dev 72 | 73 | 74 | - name: Configure CMake 75 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 76 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 77 | run: > 78 | cmake -B ${{ steps.strings.outputs.build-output-dir }} 79 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} 80 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} 81 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 82 | -S ${{ github.workspace }} 83 | 84 | - name: Build 85 | # 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). 86 | run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} 87 | 88 | - name: Test 89 | working-directory: ${{ steps.strings.outputs.build-output-dir }} 90 | # 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). 91 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 92 | run: ctest --build-config ${{ matrix.build_type }} 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug/ 2 | cmake-build-release/ 3 | .DS_Store 4 | .idea/ 5 | node_modules/ 6 | *.dSYM 7 | .vs/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay) 3 | 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 5 | 6 | option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON) 7 | option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF) 8 | option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF) 9 | option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF) 10 | option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF) 11 | option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) 12 | option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) 13 | option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) 14 | option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) 15 | 16 | message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") 17 | 18 | if(APPLE) 19 | enable_language(OBJC) 20 | endif() 21 | 22 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE) 23 | add_subdirectory("examples/cpp-project-example") 24 | endif() 25 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS) 26 | if(NOT MSVC) 27 | add_subdirectory("examples/clay-official-website") 28 | add_subdirectory("examples/terminal-example") 29 | endif() 30 | add_subdirectory("examples/introducing-clay-video-demo") 31 | endif () 32 | 33 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES) 34 | add_subdirectory("examples/raylib-multi-context") 35 | add_subdirectory("examples/raylib-sidebar-scrolling-container") 36 | endif () 37 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES) 38 | add_subdirectory("examples/SDL2-video-demo") 39 | endif () 40 | if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES)) 41 | add_subdirectory("examples/SDL3-simple-demo") 42 | endif() 43 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES) 44 | add_subdirectory("examples/sokol-video-demo") 45 | add_subdirectory("examples/sokol-corner-radius") 46 | endif() 47 | 48 | # Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement 49 | if(CLAY_INCLUDE_PLAYDATE_EXAMPLES) 50 | add_subdirectory("examples/playdate-project-example") 51 | endif() 52 | 53 | if(WIN32) # Build only for Win or Wine 54 | if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES) 55 | add_subdirectory("examples/win32_gdi") 56 | endif() 57 | endif() 58 | 59 | # add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now 60 | 61 | #add_library(${PROJECT_NAME} INTERFACE) 62 | #target_include_directories(${PROJECT_NAME} INTERFACE .) 63 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | zlib/libpng license 2 | 3 | Copyright (c) 2024 Nic Barker 4 | 5 | This software is provided 'as-is', without any express or implied warranty. 6 | In no event will the authors be held liable for any damages arising from the 7 | use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software in a 15 | product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not 19 | be misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. -------------------------------------------------------------------------------- /bindings/cpp/README.md: -------------------------------------------------------------------------------- 1 | https://github.com/TimothyHoytBSME/ClayMan -------------------------------------------------------------------------------- /bindings/csharp/README: -------------------------------------------------------------------------------- 1 | https://github.com/Orcolom/clay-cs 2 | -------------------------------------------------------------------------------- /bindings/odin/.gitignore: -------------------------------------------------------------------------------- 1 | odin 2 | odin.dSYM 3 | .vscode 4 | examples/clay-official-website/clay-official-website -------------------------------------------------------------------------------- /bindings/odin/README.md: -------------------------------------------------------------------------------- 1 | ### Odin Language Bindings 2 | 3 | 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. 4 | 5 | Special thanks to 6 | 7 | - [laytan](https://github.com/laytan) 8 | - [Dudejoe870](https://github.com/Dudejoe870) 9 | - MrStevns from the Odin Discord server 10 | 11 | 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. 12 | 13 | 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): 14 | ```C 15 | // C form of element macros 16 | // Define an element with 16px of x and y padding 17 | CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) { 18 | // Child elements here 19 | } 20 | ``` 21 | 22 | ```Odin 23 | // Odin form of element macros 24 | if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) { 25 | // Child elements here 26 | } 27 | ``` 28 | 29 | ### Quick Start 30 | 31 | 1. Download the [clay-odin](https://github.com/nicbarker/clay/tree/main/bindings/odin/clay-odin) directory and copy it into your project. 32 | 33 | ```Odin 34 | import clay "clay-odin" 35 | ``` 36 | 37 | 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). 38 | 39 | ```Odin 40 | error_handler :: proc "c" (errorData: clay.ErrorData) { 41 | // Do something with the error data. 42 | } 43 | 44 | min_memory_size := clay.MinMemorySize() 45 | memory := make([^]u8, min_memory_size) 46 | arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory) 47 | clay.Initialize(arena, {1080, 720}, { handler = error_handler }) 48 | ``` 49 | 50 | 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. 51 | 52 | ```Odin 53 | // Example measure text function 54 | measure_text :: proc "c" ( 55 | text: clay.StringSlice, 56 | config: ^clay.TextElementConfig, 57 | userData: rawptr, 58 | ) -> clay.Dimensions { 59 | // clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc.. 60 | // Note: clay.String->chars is not guaranteed to be null terminated 61 | return { 62 | width = f32(text.length * i32(config.fontSize)), 63 | height = f32(config.fontSize), 64 | } 65 | } 66 | 67 | 68 | // Tell clay how to measure text 69 | clay.SetMeasureTextFunction(measure_text, nil) 70 | ``` 71 | 72 | 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. 73 | 74 | ```Odin 75 | // Update internal pointer position for handling mouseover / click / touch events 76 | clay.SetPointerState( 77 | { mouse_pos_x, mouse_pos_y }, 78 | is_mouse_down, 79 | ) 80 | ``` 81 | 82 | 5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros. 83 | 84 | ```Odin 85 | // Define some colors. 86 | COLOR_LIGHT :: clay.Color{224, 215, 210, 255} 87 | COLOR_RED :: clay.Color{168, 66, 28, 255} 88 | COLOR_ORANGE :: clay.Color{225, 138, 50, 255} 89 | COLOR_BLACK :: clay.Color{0, 0, 0, 255} 90 | 91 | // Layout config is just a struct that can be declared statically, or inline 92 | sidebar_item_layout := clay.LayoutConfig { 93 | sizing = { 94 | width = clay.SizingGrow({}), 95 | height = clay.SizingFixed(50) 96 | }, 97 | } 98 | 99 | // Re-useable components are just normal procs. 100 | sidebar_item_component :: proc(index: u32) { 101 | if clay.UI()({ 102 | id = clay.ID("SidebarBlob", index), 103 | layout = sidebar_item_layout, 104 | backgroundColor = COLOR_ORANGE, 105 | }) {} 106 | } 107 | 108 | // An example function to create your layout tree 109 | create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) { 110 | // Begin constructing the layout. 111 | clay.BeginLayout() 112 | 113 | // An example of laying out a UI with a fixed-width sidebar and flexible-width main content 114 | // NOTE: To create a scope for child components, the Odin API uses `if` with components that have children 115 | if clay.UI()({ 116 | id = clay.ID("OuterContainer"), 117 | layout = { 118 | sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, 119 | padding = { 16, 16, 16, 16 }, 120 | childGap = 16, 121 | }, 122 | backgroundColor = { 250, 250, 255, 255 }, 123 | }) { 124 | if clay.UI()({ 125 | id = clay.ID("SideBar"), 126 | layout = { 127 | layoutDirection = .TopToBottom, 128 | sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, 129 | padding = { 16, 16, 16, 16 }, 130 | childGap = 16, 131 | }, 132 | backgroundColor = COLOR_LIGHT, 133 | }) { 134 | if clay.UI()({ 135 | id = clay.ID("ProfilePictureOuter"), 136 | layout = { 137 | sizing = { width = clay.SizingGrow({}) }, 138 | padding = { 16, 16, 16, 16 }, 139 | childGap = 16, 140 | childAlignment = { y = .Center }, 141 | }, 142 | backgroundColor = COLOR_RED, 143 | cornerRadius = { 6, 6, 6, 6 }, 144 | }) { 145 | if clay.UI()({ 146 | id = clay.ID("ProfilePicture"), 147 | layout = { 148 | sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) }, 149 | }, 150 | image = { 151 | // How you define `profile_picture` depends on your renderer. 152 | imageData = &profile_picture, 153 | sourceDimensions = { 154 | width = 60, 155 | height = 60, 156 | }, 157 | }, 158 | }) {} 159 | 160 | clay.Text( 161 | "Clay - UI Library", 162 | clay.TextConfig({ textColor = COLOR_BLACK, fontSize = 16 }), 163 | ) 164 | } 165 | 166 | // Standard Odin code like loops, etc. work inside components. 167 | // Here we render 5 sidebar items. 168 | for i in u32(0)..<5 { 169 | sidebar_item_component(i) 170 | } 171 | } 172 | 173 | if clay.UI()({ 174 | id = clay.ID("MainContent"), 175 | layout = { 176 | sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) }, 177 | }, 178 | backgroundColor = COLOR_LIGHT, 179 | }) {} 180 | } 181 | 182 | // Returns a list of render commands 183 | return clay.EndLayout() 184 | } 185 | ``` 186 | 187 | 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. 188 | 189 | ```Odin 190 | render_commands := create_layout() 191 | 192 | for i in 0.. `clay.ID` (Odin). 204 | -------------------------------------------------------------------------------- /bindings/odin/build-clay-lib.sh: -------------------------------------------------------------------------------- 1 | cp ../../clay.h clay.c; 2 | # Intel Mac 3 | 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; 4 | # ARM Mac 5 | clang -c -DCLAY_IMPLEMENTATION -g -o clay.o -static clay.c -fPIC -O3 && ar r clay-odin/macos-arm64/clay.a clay.o; 6 | # x64 Windows 7 | 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; 8 | # Linux 9 | 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; 10 | # WASM 11 | clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c; 12 | rm clay.o; 13 | rm clay.c; 14 | -------------------------------------------------------------------------------- /bindings/odin/clay-odin/clay.odin: -------------------------------------------------------------------------------- 1 | package clay 2 | 3 | import "core:c" 4 | 5 | when ODIN_OS == .Windows { 6 | foreign import Clay "windows/clay.lib" 7 | } else when ODIN_OS == .Linux { 8 | foreign import Clay "linux/clay.a" 9 | } else when ODIN_OS == .Darwin { 10 | when ODIN_ARCH == .arm64 { 11 | foreign import Clay "macos-arm64/clay.a" 12 | } else { 13 | foreign import Clay "macos/clay.a" 14 | } 15 | } else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 { 16 | foreign import Clay "wasm/clay.o" 17 | } 18 | 19 | String :: struct { 20 | isStaticallyAllocated: c.bool, 21 | length: c.int32_t, 22 | chars: [^]c.char, 23 | } 24 | 25 | StringSlice :: struct { 26 | length: c.int32_t, 27 | chars: [^]c.char, 28 | baseChars: [^]c.char, 29 | } 30 | 31 | Vector2 :: [2]c.float 32 | 33 | Dimensions :: struct { 34 | width: c.float, 35 | height: c.float, 36 | } 37 | 38 | Arena :: struct { 39 | nextAllocation: uintptr, 40 | capacity: c.size_t, 41 | memory: [^]c.char, 42 | } 43 | 44 | BoundingBox :: struct { 45 | x: c.float, 46 | y: c.float, 47 | width: c.float, 48 | height: c.float, 49 | } 50 | 51 | Color :: [4]c.float 52 | 53 | CornerRadius :: struct { 54 | topLeft: c.float, 55 | topRight: c.float, 56 | bottomLeft: c.float, 57 | bottomRight: c.float, 58 | } 59 | 60 | BorderData :: struct { 61 | width: u32, 62 | color: Color, 63 | } 64 | 65 | ElementId :: struct { 66 | id: u32, 67 | offset: u32, 68 | baseId: u32, 69 | stringId: String, 70 | } 71 | 72 | when ODIN_OS == .Windows { 73 | EnumBackingType :: u32 74 | } else { 75 | EnumBackingType :: u8 76 | } 77 | 78 | RenderCommandType :: enum EnumBackingType { 79 | None, 80 | Rectangle, 81 | Border, 82 | Text, 83 | Image, 84 | ScissorStart, 85 | ScissorEnd, 86 | Custom, 87 | } 88 | 89 | RectangleElementConfig :: struct { 90 | color: Color, 91 | } 92 | 93 | TextWrapMode :: enum EnumBackingType { 94 | Words, 95 | Newlines, 96 | None, 97 | } 98 | 99 | TextAlignment :: enum EnumBackingType { 100 | Left, 101 | Center, 102 | Right, 103 | } 104 | 105 | TextElementConfig :: struct { 106 | userData: rawptr, 107 | textColor: Color, 108 | fontId: u16, 109 | fontSize: u16, 110 | letterSpacing: u16, 111 | lineHeight: u16, 112 | wrapMode: TextWrapMode, 113 | textAlignment: TextAlignment, 114 | } 115 | 116 | AspectRatioElementConfig :: struct { 117 | aspectRatio: f32, 118 | } 119 | 120 | ImageElementConfig :: struct { 121 | imageData: rawptr, 122 | } 123 | 124 | CustomElementConfig :: struct { 125 | customData: rawptr, 126 | } 127 | 128 | BorderWidth :: struct { 129 | left: u16, 130 | right: u16, 131 | top: u16, 132 | bottom: u16, 133 | betweenChildren: u16, 134 | } 135 | 136 | BorderElementConfig :: struct { 137 | color: Color, 138 | width: BorderWidth, 139 | } 140 | 141 | ClipElementConfig :: struct { 142 | horizontal: bool, // clip overflowing elements on the "X" axis 143 | vertical: bool, // clip overflowing elements on the "Y" axis 144 | childOffset: Vector2, // offsets the [X,Y] positions of all child elements, primarily for scrolling containers 145 | } 146 | 147 | FloatingAttachPointType :: enum EnumBackingType { 148 | LeftTop, 149 | LeftCenter, 150 | LeftBottom, 151 | CenterTop, 152 | CenterCenter, 153 | CenterBottom, 154 | RightTop, 155 | RightCenter, 156 | RightBottom, 157 | } 158 | 159 | FloatingAttachPoints :: struct { 160 | element: FloatingAttachPointType, 161 | parent: FloatingAttachPointType, 162 | } 163 | 164 | PointerCaptureMode :: enum EnumBackingType { 165 | Capture, 166 | Passthrough, 167 | } 168 | 169 | FloatingAttachToElement :: enum EnumBackingType { 170 | None, 171 | Parent, 172 | ElementWithId, 173 | Root, 174 | } 175 | 176 | FloatingClipToElement :: enum EnumBackingType { 177 | None, 178 | AttachedParent, 179 | } 180 | 181 | FloatingElementConfig :: struct { 182 | offset: Vector2, 183 | expand: Dimensions, 184 | parentId: u32, 185 | zIndex: i16, 186 | attachment: FloatingAttachPoints, 187 | pointerCaptureMode: PointerCaptureMode, 188 | attachTo: FloatingAttachToElement, 189 | clipTo: FloatingClipToElement, 190 | } 191 | 192 | TextRenderData :: struct { 193 | stringContents: StringSlice, 194 | textColor: Color, 195 | fontId: u16, 196 | fontSize: u16, 197 | letterSpacing: u16, 198 | lineHeight: u16, 199 | } 200 | 201 | RectangleRenderData :: struct { 202 | backgroundColor: Color, 203 | cornerRadius: CornerRadius, 204 | } 205 | 206 | ImageRenderData :: struct { 207 | backgroundColor: Color, 208 | cornerRadius: CornerRadius, 209 | imageData: rawptr, 210 | } 211 | 212 | CustomRenderData :: struct { 213 | backgroundColor: Color, 214 | cornerRadius: CornerRadius, 215 | customData: rawptr, 216 | } 217 | 218 | BorderRenderData :: struct { 219 | color: Color, 220 | cornerRadius: CornerRadius, 221 | width: BorderWidth, 222 | } 223 | 224 | RenderCommandData :: struct #raw_union { 225 | rectangle: RectangleRenderData, 226 | text: TextRenderData, 227 | image: ImageRenderData, 228 | custom: CustomRenderData, 229 | border: BorderRenderData, 230 | } 231 | 232 | RenderCommand :: struct { 233 | boundingBox: BoundingBox, 234 | renderData: RenderCommandData, 235 | userData: rawptr, 236 | id: u32, 237 | zIndex: i16, 238 | commandType: RenderCommandType, 239 | } 240 | 241 | ScrollContainerData :: struct { 242 | // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. 243 | // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. 244 | scrollPosition: ^Vector2, 245 | scrollContainerDimensions: Dimensions, 246 | contentDimensions: Dimensions, 247 | config: ClipElementConfig, 248 | // Indicates whether an actual scroll container matched the provided ID or if the default struct was returned. 249 | found: bool, 250 | } 251 | 252 | ElementData :: struct { 253 | boundingBox: BoundingBox, 254 | found: bool, 255 | } 256 | 257 | PointerDataInteractionState :: enum EnumBackingType { 258 | PressedThisFrame, 259 | Pressed, 260 | ReleasedThisFrame, 261 | Released, 262 | } 263 | 264 | PointerData :: struct { 265 | position: Vector2, 266 | state: PointerDataInteractionState, 267 | } 268 | 269 | SizingType :: enum EnumBackingType { 270 | Fit, 271 | Grow, 272 | Percent, 273 | Fixed, 274 | } 275 | 276 | SizingConstraintsMinMax :: struct { 277 | min: c.float, 278 | max: c.float, 279 | } 280 | 281 | SizingConstraints :: struct #raw_union { 282 | sizeMinMax: SizingConstraintsMinMax, 283 | sizePercent: c.float, 284 | } 285 | 286 | SizingAxis :: struct { 287 | // Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions 288 | constraints: SizingConstraints, 289 | type: SizingType, 290 | } 291 | 292 | Sizing :: struct { 293 | width: SizingAxis, 294 | height: SizingAxis, 295 | } 296 | 297 | Padding :: struct { 298 | left: u16, 299 | right: u16, 300 | top: u16, 301 | bottom: u16, 302 | } 303 | 304 | LayoutDirection :: enum EnumBackingType { 305 | LeftToRight, 306 | TopToBottom, 307 | } 308 | 309 | LayoutAlignmentX :: enum EnumBackingType { 310 | Left, 311 | Right, 312 | Center, 313 | } 314 | 315 | LayoutAlignmentY :: enum EnumBackingType { 316 | Top, 317 | Bottom, 318 | Center, 319 | } 320 | 321 | ChildAlignment :: struct { 322 | x: LayoutAlignmentX, 323 | y: LayoutAlignmentY, 324 | } 325 | 326 | LayoutConfig :: struct { 327 | sizing: Sizing, 328 | padding: Padding, 329 | childGap: u16, 330 | childAlignment: ChildAlignment, 331 | layoutDirection: LayoutDirection, 332 | } 333 | 334 | ClayArray :: struct($type: typeid) { 335 | capacity: i32, 336 | length: i32, 337 | internalArray: [^]type, 338 | } 339 | 340 | ElementDeclaration :: struct { 341 | id: ElementId, 342 | layout: LayoutConfig, 343 | backgroundColor: Color, 344 | cornerRadius: CornerRadius, 345 | aspectRatio: AspectRatioElementConfig, 346 | image: ImageElementConfig, 347 | floating: FloatingElementConfig, 348 | custom: CustomElementConfig, 349 | clip: ClipElementConfig, 350 | border: BorderElementConfig, 351 | userData: rawptr, 352 | } 353 | 354 | ErrorType :: enum EnumBackingType { 355 | TextMeasurementFunctionNotProvided, 356 | ArenaCapacityExceeded, 357 | ElementsCapacityExceeded, 358 | TextMeasurementCapacityExceeded, 359 | DuplicateId, 360 | FloatingContainerParentNotFound, 361 | PercentageOver1, 362 | InternalError, 363 | } 364 | 365 | ErrorData :: struct { 366 | errorType: ErrorType, 367 | errorText: String, 368 | userData: rawptr, 369 | } 370 | 371 | ErrorHandler :: struct { 372 | handler: proc "c" (errorData: ErrorData), 373 | userData: rawptr, 374 | } 375 | 376 | Context :: struct {} // opaque structure, only use as a pointer 377 | 378 | @(link_prefix = "Clay_", default_calling_convention = "c") 379 | foreign Clay { 380 | _OpenElement :: proc() --- 381 | _CloseElement :: proc() --- 382 | MinMemorySize :: proc() -> u32 --- 383 | CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena --- 384 | SetPointerState :: proc(position: Vector2, pointerDown: bool) --- 385 | Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context --- 386 | GetCurrentContext :: proc() -> ^Context --- 387 | SetCurrentContext :: proc(ctx: ^Context) --- 388 | UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) --- 389 | SetLayoutDimensions :: proc(dimensions: Dimensions) --- 390 | BeginLayout :: proc() --- 391 | EndLayout :: proc() -> ClayArray(RenderCommand) --- 392 | GetElementId :: proc(id: String) -> ElementId --- 393 | GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId --- 394 | GetElementData :: proc(id: ElementId) -> ElementData --- 395 | Hovered :: proc() -> bool --- 396 | OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) --- 397 | PointerOver :: proc(id: ElementId) -> bool --- 398 | GetScrollOffset :: proc() -> Vector2 --- 399 | GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData --- 400 | SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) --- 401 | SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) --- 402 | RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand --- 403 | SetDebugModeEnabled :: proc(enabled: bool) --- 404 | IsDebugModeEnabled :: proc() -> bool --- 405 | SetCullingEnabled :: proc(enabled: bool) --- 406 | GetMaxElementCount :: proc() -> i32 --- 407 | SetMaxElementCount :: proc(maxElementCount: i32) --- 408 | GetMaxMeasureTextCacheWordCount :: proc() -> i32 --- 409 | SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) --- 410 | ResetMeasureTextCache :: proc() --- 411 | } 412 | 413 | @(link_prefix = "Clay_", default_calling_convention = "c", private) 414 | foreign Clay { 415 | _ConfigureOpenElement :: proc(config: ElementDeclaration) --- 416 | _HashString :: proc(key: String, offset: u32, seed: u32) -> ElementId --- 417 | _OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) --- 418 | _StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig --- 419 | _GetParentElementId :: proc() -> u32 --- 420 | } 421 | 422 | ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool { 423 | _ConfigureOpenElement(config) 424 | return true 425 | } 426 | 427 | @(deferred_none = _CloseElement) 428 | UI :: proc() -> proc (config: ElementDeclaration) -> bool { 429 | _OpenElement() 430 | return ConfigureOpenElement 431 | } 432 | 433 | Text :: proc($text: string, config: ^TextElementConfig) { 434 | wrapped := MakeString(text) 435 | wrapped.isStaticallyAllocated = true 436 | _OpenTextElement(wrapped, config) 437 | } 438 | 439 | TextDynamic :: proc(text: string, config: ^TextElementConfig) { 440 | _OpenTextElement(MakeString(text), config) 441 | } 442 | 443 | TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig { 444 | return _StoreTextElementConfig(config) 445 | } 446 | 447 | PaddingAll :: proc(allPadding: u16) -> Padding { 448 | return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding } 449 | } 450 | 451 | BorderOutside :: proc(width: u16) -> BorderWidth { 452 | return {width, width, width, width, 0} 453 | } 454 | 455 | BorderAll :: proc(width: u16) -> BorderWidth { 456 | return {width, width, width, width, width} 457 | } 458 | 459 | CornerRadiusAll :: proc(radius: f32) -> CornerRadius { 460 | return CornerRadius{radius, radius, radius, radius} 461 | } 462 | 463 | SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { 464 | return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}} 465 | } 466 | 467 | SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis { 468 | return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}} 469 | } 470 | 471 | SizingFixed :: proc(size: c.float) -> SizingAxis { 472 | return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}} 473 | } 474 | 475 | SizingPercent :: proc(sizePercent: c.float) -> SizingAxis { 476 | return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}} 477 | } 478 | 479 | MakeString :: proc(label: string) -> String { 480 | return String{chars = raw_data(label), length = cast(c.int)len(label)} 481 | } 482 | 483 | ID :: proc(label: string, index: u32 = 0) -> ElementId { 484 | return _HashString(MakeString(label), index, 0) 485 | } 486 | 487 | ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId { 488 | return _HashString(MakeString(label), index, _GetParentElementId()) 489 | } -------------------------------------------------------------------------------- /bindings/odin/clay-odin/linux/clay.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/clay-odin/linux/clay.a -------------------------------------------------------------------------------- /bindings/odin/clay-odin/macos-arm64/clay.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/clay-odin/macos-arm64/clay.a -------------------------------------------------------------------------------- /bindings/odin/clay-odin/macos/clay.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/clay-odin/macos/clay.a -------------------------------------------------------------------------------- /bindings/odin/clay-odin/wasm/clay.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/clay-odin/wasm/clay.o -------------------------------------------------------------------------------- /bindings/odin/clay-odin/windows/clay.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/clay-odin/windows/clay.lib -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/clay_renderer_raylib.odin: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import clay "../../clay-odin" 4 | import "core:math" 5 | import "core:strings" 6 | import rl "vendor:raylib" 7 | 8 | Raylib_Font :: struct { 9 | fontId: u16, 10 | font: rl.Font, 11 | } 12 | 13 | clay_color_to_rl_color :: proc(color: clay.Color) -> rl.Color { 14 | return {u8(color.r), u8(color.g), u8(color.b), u8(color.a)} 15 | } 16 | 17 | raylib_fonts := [dynamic]Raylib_Font{} 18 | 19 | measure_text :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions { 20 | line_width: f32 = 0 21 | 22 | font := raylib_fonts[config.fontId].font 23 | 24 | for i in 0 ..< text.length { 25 | glyph_index := text.chars[i] - 32 26 | 27 | glyph := font.glyphs[glyph_index] 28 | 29 | if glyph.advanceX != 0 { 30 | line_width += f32(glyph.advanceX) 31 | } else { 32 | line_width += font.recs[glyph_index].width + f32(glyph.offsetX) 33 | } 34 | } 35 | 36 | return {width = line_width / 2, height = f32(config.fontSize)} 37 | } 38 | 39 | clay_raylib_render :: proc(render_commands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) { 40 | for i in 0 ..< render_commands.length { 41 | render_command := clay.RenderCommandArray_Get(render_commands, i) 42 | bounds := render_command.boundingBox 43 | 44 | switch render_command.commandType { 45 | case .None: // None 46 | case .Text: 47 | config := render_command.renderData.text 48 | 49 | text := string(config.stringContents.chars[:config.stringContents.length]) 50 | 51 | // Raylib uses C strings instead of Odin strings, so we need to clone 52 | // Assume this will be freed elsewhere since we default to the temp allocator 53 | cstr_text := strings.clone_to_cstring(text, allocator) 54 | 55 | font := raylib_fonts[config.fontId].font 56 | rl.DrawTextEx(font, cstr_text, {bounds.x, bounds.y}, f32(config.fontSize), f32(config.letterSpacing), clay_color_to_rl_color(config.textColor)) 57 | case .Image: 58 | config := render_command.renderData.image 59 | tint := config.backgroundColor 60 | if tint == 0 { 61 | tint = {255, 255, 255, 255} 62 | } 63 | 64 | imageTexture := (^rl.Texture2D)(config.imageData) 65 | rl.DrawTextureEx(imageTexture^, {bounds.x, bounds.y}, 0, bounds.width / f32(imageTexture.width), clay_color_to_rl_color(tint)) 66 | case .ScissorStart: 67 | rl.BeginScissorMode(i32(math.round(bounds.x)), i32(math.round(bounds.y)), i32(math.round(bounds.width)), i32(math.round(bounds.height))) 68 | case .ScissorEnd: 69 | rl.EndScissorMode() 70 | case .Rectangle: 71 | config := render_command.renderData.rectangle 72 | if config.cornerRadius.topLeft > 0 { 73 | radius: f32 = (config.cornerRadius.topLeft * 2) / min(bounds.width, bounds.height) 74 | draw_rect_rounded(bounds.x, bounds.y, bounds.width, bounds.height, radius, config.backgroundColor) 75 | } else { 76 | draw_rect(bounds.x, bounds.y, bounds.width, bounds.height, config.backgroundColor) 77 | } 78 | case .Border: 79 | config := render_command.renderData.border 80 | // Left border 81 | if config.width.left > 0 { 82 | draw_rect( 83 | bounds.x, 84 | bounds.y + config.cornerRadius.topLeft, 85 | f32(config.width.left), 86 | bounds.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft, 87 | config.color, 88 | ) 89 | } 90 | // Right border 91 | if config.width.right > 0 { 92 | draw_rect( 93 | bounds.x + bounds.width - f32(config.width.right), 94 | bounds.y + config.cornerRadius.topRight, 95 | f32(config.width.right), 96 | bounds.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight, 97 | config.color, 98 | ) 99 | } 100 | // Top border 101 | if config.width.top > 0 { 102 | draw_rect( 103 | bounds.x + config.cornerRadius.topLeft, 104 | bounds.y, 105 | bounds.width - config.cornerRadius.topLeft - config.cornerRadius.topRight, 106 | f32(config.width.top), 107 | config.color, 108 | ) 109 | } 110 | // Bottom border 111 | if config.width.bottom > 0 { 112 | draw_rect( 113 | bounds.x + config.cornerRadius.bottomLeft, 114 | bounds.y + bounds.height - f32(config.width.bottom), 115 | bounds.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight, 116 | f32(config.width.bottom), 117 | config.color, 118 | ) 119 | } 120 | 121 | // Rounded Borders 122 | if config.cornerRadius.topLeft > 0 { 123 | draw_arc( 124 | bounds.x + config.cornerRadius.topLeft, 125 | bounds.y + config.cornerRadius.topLeft, 126 | config.cornerRadius.topLeft - f32(config.width.top), 127 | config.cornerRadius.topLeft, 128 | 180, 129 | 270, 130 | config.color, 131 | ) 132 | } 133 | if config.cornerRadius.topRight > 0 { 134 | draw_arc( 135 | bounds.x + bounds.width - config.cornerRadius.topRight, 136 | bounds.y + config.cornerRadius.topRight, 137 | config.cornerRadius.topRight - f32(config.width.top), 138 | config.cornerRadius.topRight, 139 | 270, 140 | 360, 141 | config.color, 142 | ) 143 | } 144 | if config.cornerRadius.bottomLeft > 0 { 145 | draw_arc( 146 | bounds.x + config.cornerRadius.bottomLeft, 147 | bounds.y + bounds.height - config.cornerRadius.bottomLeft, 148 | config.cornerRadius.bottomLeft - f32(config.width.top), 149 | config.cornerRadius.bottomLeft, 150 | 90, 151 | 180, 152 | config.color, 153 | ) 154 | } 155 | if config.cornerRadius.bottomRight > 0 { 156 | draw_arc( 157 | bounds.x + bounds.width - config.cornerRadius.bottomRight, 158 | bounds.y + bounds.height - config.cornerRadius.bottomRight, 159 | config.cornerRadius.bottomRight - f32(config.width.bottom), 160 | config.cornerRadius.bottomRight, 161 | 0.1, 162 | 90, 163 | config.color, 164 | ) 165 | } 166 | case clay.RenderCommandType.Custom: 167 | // Implement custom element rendering here 168 | } 169 | } 170 | } 171 | 172 | // Helper procs, mainly for repeated conversions 173 | 174 | @(private = "file") 175 | draw_arc :: proc(x, y: f32, inner_rad, outer_rad: f32,start_angle, end_angle: f32, color: clay.Color){ 176 | rl.DrawRing( 177 | {math.round(x),math.round(y)}, 178 | math.round(inner_rad), 179 | outer_rad, 180 | start_angle, 181 | end_angle, 182 | 10, 183 | clay_color_to_rl_color(color), 184 | ) 185 | } 186 | 187 | @(private = "file") 188 | draw_rect :: proc(x, y, w, h: f32, color: clay.Color) { 189 | rl.DrawRectangle( 190 | i32(math.round(x)), 191 | i32(math.round(y)), 192 | i32(math.round(w)), 193 | i32(math.round(h)), 194 | clay_color_to_rl_color(color) 195 | ) 196 | } 197 | 198 | @(private = "file") 199 | draw_rect_rounded :: proc(x,y,w,h: f32, radius: f32, color: clay.Color){ 200 | rl.DrawRectangleRounded({x,y,w,h},radius,8,clay_color_to_rl_color(color)) 201 | } -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/Calistoga-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/Calistoga-Regular.ttf -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/Quicksand-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/Quicksand-Semibold.ttf -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/check_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/check_1.png -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/check_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/check_2.png -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/check_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/check_3.png -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/check_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/check_4.png -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/check_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/check_5.png -------------------------------------------------------------------------------- /bindings/odin/examples/clay-official-website/resources/declarative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/bindings/odin/examples/clay-official-website/resources/declarative.png -------------------------------------------------------------------------------- /bindings/odin/odinfmt.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json", 3 | "character_width": 180, 4 | "sort_imports": true, 5 | "tabs": false 6 | } -------------------------------------------------------------------------------- /bindings/odin/ols.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json", 3 | "enable_document_symbols": true, 4 | "enable_hover": true, 5 | "enable_snippets": true 6 | } -------------------------------------------------------------------------------- /bindings/rust/README: -------------------------------------------------------------------------------- 1 | https://github.com/clay-ui-rs/clay 2 | https://crates.io/crates/clay-layout -------------------------------------------------------------------------------- /bindings/zig/README: -------------------------------------------------------------------------------- 1 | https://codeberg.org/Zettexe/clay-zig 2 | https://github.com/johan0A/clay-zig-bindings 3 | -------------------------------------------------------------------------------- /cmake/FindCairo.cmake: -------------------------------------------------------------------------------- 1 | # Defines: 2 | # CAIRO_FOUND - System has Cairo 3 | # CAIRO_INCLUDE_DIRS - Cairo include directories 4 | # CAIRO_LIBRARY - Cairo library 5 | # Cairo::Cairo - Imported target 6 | 7 | find_path(CAIRO_INCLUDE_DIRS 8 | NAMES cairo/cairo.h 9 | PATHS ${CAIRO_ROOT_DIR} 10 | PATH_SUFFIXES include 11 | ) 12 | 13 | find_library(CAIRO_LIBRARY 14 | NAMES cairo 15 | PATHS ${CAIRO_ROOT_DIR} 16 | PATH_SUFFIXES lib lib64 17 | ) 18 | 19 | include(FindPackageHandleStandardArgs) 20 | find_package_handle_standard_args(Cairo 21 | REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS 22 | ) 23 | 24 | if(Cairo_FOUND AND NOT TARGET Cairo::Cairo) 25 | add_library(Cairo::Cairo UNKNOWN IMPORTED) 26 | set_target_properties(Cairo::Cairo PROPERTIES 27 | IMPORTED_LOCATION "${CAIRO_LIBRARY}" 28 | INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}" 29 | ) 30 | endif() 31 | 32 | mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY) -------------------------------------------------------------------------------- /examples/SDL2-video-demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(SDL2_video_demo C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | include(FetchContent) 6 | set(FETCHCONTENT_QUIET FALSE) 7 | 8 | FetchContent_Declare( 9 | SDL2 10 | GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" 11 | GIT_TAG "release-2.30.10" 12 | GIT_PROGRESS TRUE 13 | GIT_SHALLOW TRUE 14 | ) 15 | FetchContent_MakeAvailable(SDL2) 16 | 17 | FetchContent_Declare( 18 | SDL2_ttf 19 | GIT_REPOSITORY "https://github.com/libsdl-org/SDL_ttf.git" 20 | GIT_TAG "release-2.22.0" 21 | GIT_PROGRESS TRUE 22 | GIT_SHALLOW TRUE 23 | ) 24 | FetchContent_MakeAvailable(SDL2_ttf) 25 | 26 | FetchContent_Declare( 27 | SDL2_image 28 | GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git" 29 | GIT_TAG "release-2.8.4" 30 | GIT_PROGRESS TRUE 31 | GIT_SHALLOW TRUE 32 | ) 33 | FetchContent_MakeAvailable(SDL2_image) 34 | 35 | add_executable(SDL2_video_demo main.c) 36 | 37 | target_compile_options(SDL2_video_demo PUBLIC) 38 | target_include_directories(SDL2_video_demo PUBLIC .) 39 | 40 | target_link_libraries(SDL2_video_demo PUBLIC 41 | SDL2::SDL2main 42 | SDL2::SDL2-static 43 | SDL2_ttf::SDL2_ttf-static 44 | SDL2_image::SDL2_image-static 45 | ) 46 | 47 | if(MSVC) 48 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 49 | else() 50 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 51 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 52 | endif() 53 | 54 | add_custom_command( 55 | TARGET SDL2_video_demo POST_BUILD 56 | COMMAND ${CMAKE_COMMAND} -E copy_directory 57 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 58 | ${CMAKE_CURRENT_BINARY_DIR}/resources 59 | ) 60 | -------------------------------------------------------------------------------- /examples/SDL2-video-demo/main.c: -------------------------------------------------------------------------------- 1 | #define CLAY_IMPLEMENTATION 2 | #include "../../clay.h" 3 | #include "../../renderers/SDL2/clay_renderer_SDL2.c" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "../shared-layouts/clay-video-demo.c" 13 | 14 | SDL_Surface *sample_image; 15 | 16 | void HandleClayErrors(Clay_ErrorData errorData) { 17 | printf("%s", errorData.errorText.chars); 18 | } 19 | 20 | 21 | struct ResizeRenderData_ { 22 | SDL_Window* window; 23 | int windowWidth; 24 | int windowHeight; 25 | ClayVideoDemo_Data demoData; 26 | SDL_Renderer* renderer; 27 | SDL2_Font* fonts; 28 | }; 29 | typedef struct ResizeRenderData_ ResizeRenderData; 30 | 31 | int resizeRendering(void* userData, SDL_Event* event) { 32 | ResizeRenderData *actualData = userData; 33 | if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED) { 34 | SDL_Window* window = actualData->window; 35 | int windowWidth = actualData->windowWidth; 36 | int windowHeight = actualData->windowHeight; 37 | ClayVideoDemo_Data demoData = actualData->demoData; 38 | SDL_Renderer* renderer = actualData->renderer; 39 | SDL2_Font* fonts = actualData->fonts; 40 | 41 | SDL_GetWindowSize(window, &windowWidth, &windowHeight); 42 | Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); 43 | 44 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); 45 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 46 | SDL_RenderClear(renderer); 47 | 48 | Clay_SDL2_Render(renderer, renderCommands, fonts); 49 | 50 | SDL_RenderPresent(renderer); 51 | } 52 | return 0; 53 | } 54 | 55 | int main(int argc, char *argv[]) { 56 | if (SDL_Init(SDL_INIT_VIDEO) < 0) { 57 | fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError()); 58 | return 1; 59 | } 60 | if (TTF_Init() < 0) { 61 | fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError()); 62 | return 1; 63 | } 64 | if (IMG_Init(IMG_INIT_PNG) < 0) { 65 | fprintf(stderr, "Error: could not initialize IMG: %s\n", IMG_GetError()); 66 | return 1; 67 | } 68 | 69 | TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 16); 70 | if (!font) { 71 | fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError()); 72 | return 1; 73 | } 74 | 75 | SDL2_Font fonts[1] = {}; 76 | 77 | fonts[FONT_ID_BODY_16] = (SDL2_Font) { 78 | .fontId = FONT_ID_BODY_16, 79 | .font = font, 80 | }; 81 | 82 | sample_image = IMG_Load("resources/sample.png"); 83 | 84 | SDL_Window *window = NULL; 85 | SDL_Renderer *renderer = NULL; 86 | 87 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); //for antialiasing 88 | window = SDL_CreateWindow("SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 89 | SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //for antialiasing 90 | 91 | bool enableVsync = false; 92 | if(enableVsync){ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);} //"SDL_RENDERER_ACCELERATED" is for antialiasing 93 | else{renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);} 94 | SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); //for alpha blending 95 | 96 | uint64_t totalMemorySize = Clay_MinMemorySize(); 97 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); 98 | 99 | int windowWidth = 0; 100 | int windowHeight = 0; 101 | SDL_GetWindowSize(window, &windowWidth, &windowHeight); 102 | Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors }); 103 | 104 | Clay_SetMeasureTextFunction(SDL2_MeasureText, &fonts); 105 | 106 | Uint64 NOW = SDL_GetPerformanceCounter(); 107 | Uint64 LAST = 0; 108 | double deltaTime = 0; 109 | ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); 110 | 111 | 112 | ResizeRenderData userData = { 113 | window, // SDL_Window* 114 | windowWidth, // int 115 | windowHeight, // int 116 | demoData, // CustomShit 117 | renderer, // SDL_Renderer* 118 | fonts // SDL2_Font[1] 119 | }; 120 | // add an event watcher that will render the screen while youre dragging the window to different sizes 121 | SDL_AddEventWatch(resizeRendering, &userData); 122 | 123 | while (true) { 124 | Clay_Vector2 scrollDelta = {}; 125 | SDL_Event event; 126 | while (SDL_PollEvent(&event)) { 127 | switch (event.type) { 128 | case SDL_QUIT: { goto quit; } 129 | case SDL_MOUSEWHEEL: { 130 | scrollDelta.x = event.wheel.x; 131 | scrollDelta.y = event.wheel.y; 132 | break; 133 | } 134 | } 135 | } 136 | LAST = NOW; 137 | NOW = SDL_GetPerformanceCounter(); 138 | deltaTime = (double)((NOW - LAST)*1000 / (double)SDL_GetPerformanceFrequency() ); 139 | printf("%f\n", deltaTime); 140 | 141 | int mouseX = 0; 142 | int mouseY = 0; 143 | Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY); 144 | Clay_Vector2 mousePosition = (Clay_Vector2){ (float)mouseX, (float)mouseY }; 145 | Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1)); 146 | 147 | Clay_UpdateScrollContainers( 148 | true, 149 | (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, 150 | deltaTime 151 | ); 152 | 153 | SDL_GetWindowSize(window, &windowWidth, &windowHeight); 154 | Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight }); 155 | 156 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); 157 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); 158 | SDL_RenderClear(renderer); 159 | 160 | Clay_SDL2_Render(renderer, renderCommands, fonts); 161 | 162 | SDL_RenderPresent(renderer); 163 | } 164 | 165 | quit: 166 | SDL_DestroyRenderer(renderer); 167 | SDL_DestroyWindow(window); 168 | IMG_Quit(); 169 | TTF_Quit(); 170 | SDL_Quit(); 171 | return 0; 172 | } 173 | 174 | -------------------------------------------------------------------------------- /examples/SDL2-video-demo/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/SDL2-video-demo/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/SDL2-video-demo/resources/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/SDL2-video-demo/resources/sample.png -------------------------------------------------------------------------------- /examples/SDL3-simple-demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | 3 | # Project setup 4 | project(clay_examples_sdl3_simple_demo C) 5 | set(CMAKE_C_STANDARD 99) 6 | 7 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 8 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 9 | 10 | include(FetchContent) 11 | set(FETCHCONTENT_QUIET FALSE) 12 | 13 | # Download SDL3 14 | FetchContent_Declare( 15 | SDL 16 | GIT_REPOSITORY https://github.com/libsdl-org/SDL.git 17 | GIT_TAG release-3.2.4 18 | GIT_SHALLOW TRUE 19 | GIT_PROGRESS TRUE 20 | ) 21 | message(STATUS "Using SDL via FetchContent") 22 | FetchContent_MakeAvailable(SDL) 23 | set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) 24 | 25 | # Download SDL_ttf 26 | FetchContent_Declare( 27 | SDL_ttf 28 | GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git 29 | GIT_TAG release-3.2.2 30 | GIT_SHALLOW TRUE 31 | GIT_PROGRESS TRUE 32 | ) 33 | message(STATUS "Using SDL_ttf via FetchContent") 34 | FetchContent_MakeAvailable(SDL_ttf) 35 | set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) 36 | 37 | # Download SDL_image 38 | FetchContent_Declare( 39 | SDL_image 40 | GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git" 41 | GIT_TAG release-3.2.0 42 | GIT_SHALLOW TRUE 43 | GIT_PROGRESS TRUE 44 | ) 45 | message(STATUS "Using SDL_image via FetchContent") 46 | FetchContent_MakeAvailable(SDL_image) 47 | set_property(DIRECTORY "${SDL_image_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) 48 | 49 | # Example executable 50 | add_executable(${PROJECT_NAME} main.c) 51 | target_link_libraries(${PROJECT_NAME} PRIVATE 52 | SDL3::SDL3 53 | SDL3_ttf::SDL3_ttf 54 | SDL3_image::SDL3_image 55 | ) 56 | 57 | add_custom_command( 58 | TARGET ${PROJECT_NAME} POST_BUILD 59 | COMMAND ${CMAKE_COMMAND} -E copy_directory 60 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 61 | ${CMAKE_CURRENT_BINARY_DIR}/resources 62 | ) 63 | -------------------------------------------------------------------------------- /examples/SDL3-simple-demo/main.c: -------------------------------------------------------------------------------- 1 | #define SDL_MAIN_USE_CALLBACKS 2 | #include 3 | #include 4 | #include 5 | 6 | #define CLAY_IMPLEMENTATION 7 | #include "../../clay.h" 8 | 9 | #include 10 | 11 | #include "../../renderers/SDL3/clay_renderer_SDL3.c" 12 | #include "../shared-layouts/clay-video-demo.c" 13 | 14 | static const Uint32 FONT_ID = 0; 15 | 16 | static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; 17 | static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255}; 18 | static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; 19 | 20 | typedef struct app_state { 21 | SDL_Window *window; 22 | Clay_SDL3RendererData rendererData; 23 | ClayVideoDemo_Data demoData; 24 | } AppState; 25 | 26 | SDL_Texture *sample_image; 27 | bool show_demo = true; 28 | 29 | static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) 30 | { 31 | TTF_Font **fonts = userData; 32 | TTF_Font *font = fonts[config->fontId]; 33 | int width, height; 34 | 35 | if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { 36 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); 37 | } 38 | 39 | return (Clay_Dimensions) { (float) width, (float) height }; 40 | } 41 | 42 | void HandleClayErrors(Clay_ErrorData errorData) { 43 | printf("%s", errorData.errorText.chars); 44 | } 45 | 46 | 47 | Clay_RenderCommandArray ClayImageSample_CreateLayout() { 48 | Clay_BeginLayout(); 49 | 50 | Clay_Sizing layoutExpand = { 51 | .width = CLAY_SIZING_GROW(0), 52 | .height = CLAY_SIZING_GROW(0) 53 | }; 54 | 55 | CLAY({ .id = CLAY_ID("OuterContainer"), 56 | .layout = { 57 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 58 | .sizing = layoutExpand, 59 | .padding = CLAY_PADDING_ALL(16), 60 | .childGap = 16 61 | } 62 | }) { 63 | CLAY({ 64 | .id = CLAY_ID("SampleImage"), 65 | .layout = { 66 | .sizing = layoutExpand 67 | }, 68 | .image = { 69 | .imageData = sample_image, 70 | .sourceDimensions = { 71 | .width = 23, 72 | .height = 42 73 | }, 74 | } 75 | }); 76 | } 77 | 78 | return Clay_EndLayout(); 79 | } 80 | 81 | 82 | SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) 83 | { 84 | (void) argc; 85 | (void) argv; 86 | 87 | if (!TTF_Init()) { 88 | return SDL_APP_FAILURE; 89 | } 90 | 91 | AppState *state = SDL_calloc(1, sizeof(AppState)); 92 | if (!state) { 93 | return SDL_APP_FAILURE; 94 | } 95 | *appstate = state; 96 | 97 | if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->rendererData.renderer)) { 98 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError()); 99 | return SDL_APP_FAILURE; 100 | } 101 | SDL_SetWindowResizable(state->window, true); 102 | 103 | state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer); 104 | if (!state->rendererData.textEngine) { 105 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError()); 106 | return SDL_APP_FAILURE; 107 | } 108 | 109 | state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *)); 110 | if (!state->rendererData.fonts) { 111 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError()); 112 | return SDL_APP_FAILURE; 113 | } 114 | 115 | TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24); 116 | if (!font) { 117 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError()); 118 | return SDL_APP_FAILURE; 119 | } 120 | 121 | state->rendererData.fonts[FONT_ID] = font; 122 | 123 | sample_image = IMG_LoadTexture(state->rendererData.renderer, "resources/sample.png"); 124 | if (!sample_image) { 125 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load image: %s", SDL_GetError()); 126 | return SDL_APP_FAILURE; 127 | } 128 | 129 | /* Initialize Clay */ 130 | uint64_t totalMemorySize = Clay_MinMemorySize(); 131 | Clay_Arena clayMemory = (Clay_Arena) { 132 | .memory = SDL_malloc(totalMemorySize), 133 | .capacity = totalMemorySize 134 | }; 135 | 136 | int width, height; 137 | SDL_GetWindowSize(state->window, &width, &height); 138 | Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors }); 139 | Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts); 140 | 141 | state->demoData = ClayVideoDemo_Initialize(); 142 | 143 | *appstate = state; 144 | return SDL_APP_CONTINUE; 145 | } 146 | 147 | SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) 148 | { 149 | SDL_AppResult ret_val = SDL_APP_CONTINUE; 150 | 151 | switch (event->type) { 152 | case SDL_EVENT_QUIT: 153 | ret_val = SDL_APP_SUCCESS; 154 | break; 155 | case SDL_EVENT_KEY_UP: 156 | if (event->key.scancode == SDL_SCANCODE_SPACE) { 157 | show_demo = !show_demo; 158 | } 159 | break; 160 | case SDL_EVENT_WINDOW_RESIZED: 161 | Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 }); 162 | break; 163 | case SDL_EVENT_MOUSE_MOTION: 164 | Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y }, 165 | event->motion.state & SDL_BUTTON_LMASK); 166 | break; 167 | case SDL_EVENT_MOUSE_BUTTON_DOWN: 168 | Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y }, 169 | event->button.button == SDL_BUTTON_LEFT); 170 | break; 171 | case SDL_EVENT_MOUSE_WHEEL: 172 | Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f); 173 | break; 174 | default: 175 | break; 176 | }; 177 | 178 | return ret_val; 179 | } 180 | 181 | SDL_AppResult SDL_AppIterate(void *appstate) 182 | { 183 | AppState *state = appstate; 184 | 185 | Clay_RenderCommandArray render_commands = (show_demo 186 | ? ClayVideoDemo_CreateLayout(&state->demoData) 187 | : ClayImageSample_CreateLayout() 188 | ); 189 | 190 | SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255); 191 | SDL_RenderClear(state->rendererData.renderer); 192 | 193 | SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands); 194 | 195 | SDL_RenderPresent(state->rendererData.renderer); 196 | 197 | return SDL_APP_CONTINUE; 198 | } 199 | 200 | void SDL_AppQuit(void *appstate, SDL_AppResult result) 201 | { 202 | (void) result; 203 | 204 | if (result != SDL_APP_SUCCESS) { 205 | SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run"); 206 | } 207 | 208 | AppState *state = appstate; 209 | 210 | if (sample_image) { 211 | SDL_DestroyTexture(sample_image); 212 | } 213 | 214 | if (state) { 215 | if (state->rendererData.renderer) 216 | SDL_DestroyRenderer(state->rendererData.renderer); 217 | 218 | if (state->window) 219 | SDL_DestroyWindow(state->window); 220 | 221 | if (state->rendererData.fonts) { 222 | for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) { 223 | TTF_CloseFont(state->rendererData.fonts[i]); 224 | } 225 | 226 | SDL_free(state->rendererData.fonts); 227 | } 228 | 229 | if (state->rendererData.textEngine) 230 | TTF_DestroyRendererTextEngine(state->rendererData.textEngine); 231 | 232 | SDL_free(state); 233 | } 234 | TTF_Quit(); 235 | } 236 | -------------------------------------------------------------------------------- /examples/SDL3-simple-demo/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/SDL3-simple-demo/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/SDL3-simple-demo/resources/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/SDL3-simple-demo/resources/sample.png -------------------------------------------------------------------------------- /examples/cairo-pdf-rendering/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_cairo_pdf_rendering C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake") 6 | 7 | add_executable(clay_examples_cairo_pdf_rendering main.c) 8 | 9 | find_package(Cairo REQUIRED) 10 | 11 | target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC) 12 | target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS}) 13 | 14 | target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC Cairo::Cairo) 15 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 16 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 17 | 18 | add_custom_command( 19 | TARGET clay_examples_cairo_pdf_rendering POST_BUILD 20 | COMMAND ${CMAKE_COMMAND} -E copy_directory 21 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 22 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 23 | -------------------------------------------------------------------------------- /examples/cairo-pdf-rendering/main.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Justin Andreas Lacoste (@27justin) 2 | // 3 | // This software is provided 'as-is', without any express or implied warranty. 4 | // In no event will the authors be held liable for any damages arising from the 5 | // use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software in a 13 | // product, an acknowledgment in the product documentation would be 14 | // appreciated but is not required. 15 | // 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | // SPDX-License-Identifier: Zlib 23 | 24 | #include 25 | 26 | // The renderer includes clay.h while also providing the 27 | // CLAY_IMPLEMENTATION 28 | #include "../../renderers/cairo/clay_renderer_cairo.c" 29 | 30 | // cairo-pdf, though this is optional and not required if you, 31 | // e.g. render PNGs. 32 | #include 33 | 34 | const uint16_t FONT_CALLISTOGA = 0; 35 | const uint16_t FONT_QUICKSAND = 0; 36 | 37 | // Layout the first page. 38 | void Layout() { 39 | static Clay_Color PRIMARY = { 0xa8, 0x42, 0x1c, 255 }; 40 | static Clay_Color BACKGROUND = { 0xF4, 0xEB, 0xE6, 255 }; 41 | static Clay_Color ACCENT = { 0xFA, 0xE0, 0xD4, 255 }; 42 | 43 | CLAY({ 44 | .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, 45 | .layoutDirection = CLAY_TOP_TO_BOTTOM }, 46 | .backgroundColor = BACKGROUND 47 | }) { 48 | CLAY({ .id = CLAY_ID("PageMargins"), .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, 49 | .padding = { 70, 70, 50, 50 }, // Some nice looking page margins 50 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 51 | .childGap = 10} 52 | }) { 53 | // Section Title 54 | CLAY_TEXT(CLAY_STRING("Features Overview"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .textColor = PRIMARY, .fontSize = 24 })); 55 | 56 | // Feature Box 57 | CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }, .childGap = 10 }}) { 58 | CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }}, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(12) }) { 59 | CLAY({ .layout = {.padding = CLAY_PADDING_ALL(20), .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { 60 | CLAY_TEXT(CLAY_STRING("- High performance"), 61 | CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); 62 | CLAY_TEXT(CLAY_STRING("- Declarative syntax"), 63 | CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); 64 | CLAY_TEXT(CLAY_STRING("- Flexbox-style responsive layout"), 65 | CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); 66 | CLAY_TEXT(CLAY_STRING("- Single .h file for C/C++"), 67 | CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); 68 | CLAY_TEXT(CLAY_STRING("- And now with cairo!"), 69 | CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND })); 70 | } 71 | } 72 | CLAY({ 73 | .layout = { 74 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, 75 | .padding = CLAY_PADDING_ALL(10), 76 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 77 | .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }, 78 | .childGap = 4 79 | }, 80 | .backgroundColor = ACCENT, 81 | .cornerRadius = CLAY_CORNER_RADIUS(8) 82 | }) { 83 | // Profile picture 84 | CLAY({ .layout = { 85 | .sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)}, 86 | .padding = { 30, 30, 0, 0 }, 87 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 88 | .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }}, 89 | .border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10 90 | }) { 91 | CLAY({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 32, 32 }, .imageData = "resources/check.png" }}); 92 | } 93 | } 94 | } 95 | 96 | CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(16) } }}); 97 | 98 | CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childGap = 10, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) { 99 | CLAY_TEXT(CLAY_STRING("Cairo"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .fontSize = 24, .textColor = PRIMARY })); 100 | CLAY({ .layout = { .padding = CLAY_PADDING_ALL(10) }, .backgroundColor = ACCENT, .cornerRadius = 10 }) { 101 | 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. " 102 | "Deleniti saepe nihil exercitationem nemo illo. Consequatur beatae repellat provident similique. Provident qui exercitationem deserunt sapiente. Quam qui dolor corporis odit. " 103 | "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. " 104 | "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. " 105 | "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 })); 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | void HandleClayErrors(Clay_ErrorData errorData) { 113 | printf("%s", errorData.errorText.chars); 114 | } 115 | 116 | int main(void) { 117 | // First we set up our cairo surface. 118 | // In this example we will use the PDF backend, 119 | // but you should be able to use any of them. 120 | // Guaranteed to be working are: PDF, PNG 121 | 122 | // Create a PDF surface that is the same size as a DIN A4 sheet 123 | // When using the PDF backend, cairo calculates in points (1 point == 1/72.0 inch) 124 | double width = (21.0 / 2.54) * 72, // cm in points 125 | height = (29.7 / 2.54) * 72; 126 | 127 | cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height); 128 | cairo_t *cr = cairo_create(surface); 129 | cairo_surface_destroy(surface); // Drop reference 130 | 131 | // Initialize internal global variable with `cr`. 132 | // We require some kind of global reference to a valid 133 | // cairo instance to properly measure text. 134 | // Note that due to this, this interface is not thread-safe! 135 | Clay_Cairo_Initialize(cr); 136 | 137 | uint64_t totalMemorySize = Clay_MinMemorySize(); 138 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); 139 | 140 | // We initialize Clay with the same size 141 | Clay_Initialize(clayMemory, (Clay_Dimensions) { width, height }, (Clay_ErrorHandler) { HandleClayErrors }); 142 | 143 | char** fonts = (char*[]) { 144 | "Callistoga", 145 | "Quicksand Semibold" 146 | }; 147 | 148 | Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, fonts); 149 | 150 | Clay_BeginLayout(); 151 | 152 | // Here you can now create the declarative clay layout. 153 | // Moved into a separate function for brevity. 154 | Layout(); 155 | 156 | Clay_RenderCommandArray commands = Clay_EndLayout(); 157 | // Pass our layout to the cairo backend 158 | Clay_Cairo_Render(commands, fonts); 159 | 160 | // To keep this example short, we will not emit a second page in the PDF. 161 | // But to do so, you have to 162 | // 1. cairo_show_page(cr) 163 | // 2. Clay_BeginLayout(); 164 | // 3. Create your layout 165 | // 4. commands = Clay_EndLayout(); 166 | // 5. Clay_Cairo_Render(commands); 167 | 168 | cairo_destroy(cr); 169 | return 0; 170 | } 171 | 172 | -------------------------------------------------------------------------------- /examples/cairo-pdf-rendering/resources/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/cairo-pdf-rendering/resources/check.png -------------------------------------------------------------------------------- /examples/clay-official-website/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_official_website C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | add_executable(clay_official_website main.c) 7 | 8 | target_compile_options(clay_official_website PUBLIC) 9 | target_include_directories(clay_official_website PUBLIC .) 10 | -------------------------------------------------------------------------------- /examples/clay-official-website/build.sh: -------------------------------------------------------------------------------- 1 | mkdir -p build/clay \ 2 | && clang \ 3 | -Wall \ 4 | -Werror \ 5 | -Os \ 6 | -DCLAY_WASM \ 7 | -mbulk-memory \ 8 | --target=wasm32 \ 9 | -nostdlib \ 10 | -Wl,--strip-all \ 11 | -Wl,--export-dynamic \ 12 | -Wl,--no-entry \ 13 | -Wl,--export=__heap_base \ 14 | -Wl,--export=ACTIVE_RENDERER_INDEX \ 15 | -Wl,--initial-memory=6553600 \ 16 | -o build/clay/index.wasm \ 17 | main.c \ 18 | && cp index.html build/clay/index.html && cp -r fonts/ build/clay/fonts \ 19 | && cp index.html build/clay/index.html && cp -r images/ build/clay/images -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/fonts/Calistoga-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/fonts/Calistoga-Regular.ttf -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/fonts/Quicksand-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/fonts/Quicksand-Semibold.ttf -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/check_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/check_1.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/check_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/check_2.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/check_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/check_3.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/check_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/check_4.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/check_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/check_5.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/debugger.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/declarative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/declarative.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/images/renderer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/images/renderer.png -------------------------------------------------------------------------------- /examples/clay-official-website/build/clay/index.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/build/clay/index.wasm -------------------------------------------------------------------------------- /examples/clay-official-website/fonts/Calistoga-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/fonts/Calistoga-Regular.ttf -------------------------------------------------------------------------------- /examples/clay-official-website/fonts/Quicksand-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/fonts/Quicksand-Semibold.ttf -------------------------------------------------------------------------------- /examples/clay-official-website/images/check_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/check_1.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/check_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/check_2.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/check_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/check_3.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/check_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/check_4.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/check_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/check_5.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/debugger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/debugger.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/declarative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/declarative.png -------------------------------------------------------------------------------- /examples/clay-official-website/images/renderer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/clay-official-website/images/renderer.png -------------------------------------------------------------------------------- /examples/cpp-project-example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_cpp_project_example CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | if(NOT MSVC) 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g") 7 | endif() 8 | 9 | add_executable(clay_examples_cpp_project_example main.cpp) 10 | 11 | target_include_directories(clay_examples_cpp_project_example PUBLIC .) 12 | 13 | if(NOT MSVC) 14 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 15 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 16 | endif() 17 | -------------------------------------------------------------------------------- /examples/cpp-project-example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #define CLAY_IMPLEMENTATION 3 | #include "../../clay.h" 4 | 5 | Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} }; 6 | 7 | void HandleClayErrors(Clay_ErrorData errorData) { 8 | printf("%s", errorData.errorText.chars); 9 | } 10 | 11 | int main(void) { 12 | uint64_t totalMemorySize = Clay_MinMemorySize(); 13 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize)); 14 | Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors }); 15 | Clay_BeginLayout(); 16 | CLAY({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) { 17 | CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 })); 18 | } 19 | Clay_EndLayout(); 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /examples/introducing-clay-video-demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_introducing_clay_video_demo C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | # Adding Raylib 6 | include(FetchContent) 7 | set(FETCHCONTENT_QUIET FALSE) 8 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 9 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 10 | 11 | FetchContent_Declare( 12 | raylib 13 | GIT_REPOSITORY "https://github.com/raysan5/raylib.git" 14 | GIT_TAG "5.5" 15 | GIT_PROGRESS TRUE 16 | GIT_SHALLOW TRUE 17 | ) 18 | 19 | FetchContent_MakeAvailable(raylib) 20 | 21 | add_executable(clay_examples_introducing_clay_video_demo main.c) 22 | 23 | target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC) 24 | target_include_directories(clay_examples_introducing_clay_video_demo PUBLIC .) 25 | 26 | target_link_libraries(clay_examples_introducing_clay_video_demo PUBLIC raylib) 27 | 28 | if(MSVC) 29 | set(CMAKE_C_FLAGS_DEBUG "/D CLAY_DEBUG") 30 | else() 31 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 32 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 33 | endif() 34 | 35 | add_custom_command( 36 | TARGET clay_examples_introducing_clay_video_demo POST_BUILD 37 | COMMAND ${CMAKE_COMMAND} -E copy_directory 38 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 39 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 40 | -------------------------------------------------------------------------------- /examples/introducing-clay-video-demo/main.c: -------------------------------------------------------------------------------- 1 | #define CLAY_IMPLEMENTATION 2 | #include "../../clay.h" 3 | #include "../../renderers/raylib/clay_renderer_raylib.c" 4 | #include "../shared-layouts/clay-video-demo.c" 5 | 6 | // This function is new since the video was published 7 | void HandleClayErrors(Clay_ErrorData errorData) { 8 | printf("%s", errorData.errorText.chars); 9 | } 10 | 11 | int main(void) { 12 | 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 13 | 14 | uint64_t clayRequiredMemory = Clay_MinMemorySize(); 15 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); 16 | Clay_Initialize(clayMemory, (Clay_Dimensions) { 17 | .width = GetScreenWidth(), 18 | .height = GetScreenHeight() 19 | }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published 20 | Font fonts[1]; 21 | fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); 22 | SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); 23 | Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); 24 | 25 | ClayVideoDemo_Data data = ClayVideoDemo_Initialize(); 26 | 27 | while (!WindowShouldClose()) { 28 | // Run once per frame 29 | Clay_SetLayoutDimensions((Clay_Dimensions) { 30 | .width = GetScreenWidth(), 31 | .height = GetScreenHeight() 32 | }); 33 | 34 | Vector2 mousePosition = GetMousePosition(); 35 | Vector2 scrollDelta = GetMouseWheelMoveV(); 36 | Clay_SetPointerState( 37 | (Clay_Vector2) { mousePosition.x, mousePosition.y }, 38 | IsMouseButtonDown(0) 39 | ); 40 | Clay_UpdateScrollContainers( 41 | true, 42 | (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, 43 | GetFrameTime() 44 | ); 45 | 46 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&data); 47 | 48 | BeginDrawing(); 49 | ClearBackground(BLACK); 50 | Clay_Raylib_Render(renderCommands, fonts); 51 | EndDrawing(); 52 | } 53 | // This function is new since the video was published 54 | Clay_Raylib_Close(); 55 | } 56 | -------------------------------------------------------------------------------- /examples/introducing-clay-video-demo/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/introducing-clay-video-demo/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/playdate-project-example/.gitignore: -------------------------------------------------------------------------------- 1 | clay_playdate_example.pdx 2 | Source/pdex.dylib 3 | Source/pdex.elf 4 | -------------------------------------------------------------------------------- /examples/playdate-project-example/CmakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | set(CMAKE_C_STANDARD 99) 3 | 4 | set(ENVSDK $ENV{PLAYDATE_SDK_PATH}) 5 | 6 | if (NOT ${ENVSDK} STREQUAL "") 7 | # Convert path from Windows 8 | file(TO_CMAKE_PATH ${ENVSDK} SDK) 9 | else() 10 | execute_process( 11 | COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config" 12 | COMMAND head -n 1 13 | COMMAND cut -c9- 14 | OUTPUT_VARIABLE SDK 15 | OUTPUT_STRIP_TRAILING_WHITESPACE 16 | ) 17 | endif() 18 | 19 | if (NOT EXISTS ${SDK}) 20 | message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH") 21 | return() 22 | endif() 23 | 24 | set(CMAKE_CONFIGURATION_TYPES "Debug;Release") 25 | set(CMAKE_XCODE_GENERATE_SCHEME TRUE) 26 | 27 | # Game Name Customization 28 | set(PLAYDATE_GAME_NAME clay_playdate_example) 29 | set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE) 30 | 31 | project(${PLAYDATE_GAME_NAME} C ASM) 32 | 33 | if (TOOLCHAIN STREQUAL "armgcc") 34 | add_executable(${PLAYDATE_GAME_DEVICE} main.c) 35 | else() 36 | add_library(${PLAYDATE_GAME_NAME} SHARED main.c) 37 | endif() 38 | 39 | include(${SDK}/C_API/buildsupport/playdate_game.cmake) 40 | 41 | -------------------------------------------------------------------------------- /examples/playdate-project-example/README.md: -------------------------------------------------------------------------------- 1 | # Playdate console example 2 | 3 | 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. 4 | 5 | ## Building 6 | 7 | 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. 8 | 9 | e.g. 10 | 11 | ``` 12 | cmake -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON cmake-build-debug 13 | ``` 14 | 15 | And then build it: 16 | 17 | ``` 18 | cmake --build cmake-build-debug 19 | ``` 20 | 21 | The pdx file will be located at examples/playdate-project-example/clay_playdate_example.pdx. You can then open it with the Playdate simulator. 22 | 23 | ## Building for the playdate device 24 | 25 | 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. 26 | 27 | e.g. To setup the cmake-build-release directory for device builds: 28 | 29 | ``` 30 | 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 31 | ``` 32 | 33 | And then build it: 34 | 35 | ``` 36 | cmake --build cmake-build-release 37 | ``` 38 | -------------------------------------------------------------------------------- /examples/playdate-project-example/Source/pdxinfo: -------------------------------------------------------------------------------- 1 | name=Clay Playdate Example 2 | author=Matthew Jennings 3 | description=A small demo of Clay running on the Playdate 4 | bundleID=dev.mattahj.clay_example 5 | imagePath= 6 | -------------------------------------------------------------------------------- /examples/playdate-project-example/Source/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/playdate-project-example/Source/star.png -------------------------------------------------------------------------------- /examples/playdate-project-example/main.c: -------------------------------------------------------------------------------- 1 | #include "pd_api.h" 2 | #define CLAY_IMPLEMENTATION 3 | #include "../../clay.h" 4 | 5 | #include "../../renderers/playdate/clay_renderer_playdate.c" 6 | #include "clay-video-demo-playdate.c" 7 | 8 | static int update(void *userdata); 9 | 10 | #define NUM_FONTS 2 11 | const char *fontsToLoad[NUM_FONTS] = { 12 | "/System/Fonts/Asheville-Sans-14-Bold.pft", 13 | "/System/Fonts/Roobert-10-Bold.pft" 14 | }; 15 | 16 | void HandleClayErrors(Clay_ErrorData errorData) {} 17 | 18 | struct TextUserData { 19 | LCDFont *font[NUM_FONTS]; 20 | PlaydateAPI *pd; 21 | }; 22 | 23 | static struct TextUserData textUserData = { .font = { NULL }, .pd = NULL }; 24 | 25 | static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { 26 | struct TextUserData *textUserData = userData; 27 | int width = textUserData->pd->graphics->getTextWidth( 28 | textUserData->font[config->fontId], 29 | text.chars, 30 | Clay_Playdate_CountUtf8Codepoints(text.chars, text.length), 31 | kUTF8Encoding, 32 | 0 33 | ); 34 | int height = textUserData->pd->graphics->getFontHeight(textUserData->font[config->fontId]); 35 | return (Clay_Dimensions){ 36 | .width = (float)width, 37 | .height = (float)height, 38 | }; 39 | } 40 | 41 | #ifdef _WINDLL 42 | __declspec(dllexport) 43 | #endif 44 | int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t eventArg) { 45 | if (event == kEventInit) { 46 | const char *err; 47 | for (int i = 0; i < NUM_FONTS; ++i) { 48 | 49 | textUserData.font[i] = pd->graphics->loadFont(fontsToLoad[i], &err); 50 | if (textUserData.font[i] == NULL) { 51 | pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontsToLoad[i], err); 52 | } 53 | } 54 | 55 | textUserData.pd = pd; 56 | pd->system->setUpdateCallback(update, pd); 57 | 58 | uint64_t totalMemorySize = Clay_MinMemorySize(); 59 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory( 60 | totalMemorySize, 61 | pd->system->realloc(NULL, totalMemorySize) 62 | ); 63 | Clay_Initialize( 64 | clayMemory, 65 | (Clay_Dimensions){ 66 | (float)pd->display->getWidth(), 67 | (float)pd->display->getHeight() 68 | }, 69 | (Clay_ErrorHandler){HandleClayErrors} 70 | ); 71 | Clay_SetMeasureTextFunction(PlayDate_MeasureText, &textUserData); 72 | ClayVideoDemoPlaydate_Initialize(pd); 73 | } 74 | 75 | return 0; 76 | } 77 | 78 | int selectedDocumentIndex = 0; 79 | #define WRAP_RANGE(x, N) ((((x) % (N)) + (N)) % (N)) 80 | 81 | static int update(void *userdata) { 82 | PlaydateAPI *pd = userdata; 83 | PDButtons pushedButtons; 84 | pd->system->getButtonState(NULL, &pushedButtons, NULL); 85 | 86 | if (pushedButtons & kButtonDown) { 87 | selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex + 1, MAX_DOCUMENTS); 88 | } else if (pushedButtons & kButtonUp) { 89 | selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex - 1, MAX_DOCUMENTS); 90 | } 91 | 92 | pd->graphics->clear(kColorWhite); 93 | 94 | // A bit hacky, setting the cursor on to the document view so it can be 95 | // scrolled.. 96 | Clay_SetPointerState( 97 | (Clay_Vector2){ 98 | .x = pd->display->getWidth() / 2.0f, 99 | .y = pd->display->getHeight() / 2.0f 100 | }, 101 | false 102 | ); 103 | 104 | float crankDelta = pd->system->getCrankChange(); 105 | Clay_UpdateScrollContainers( 106 | false, 107 | (Clay_Vector2){ 0, -crankDelta * 0.25f }, 108 | pd->system->getElapsedTime() 109 | ); 110 | 111 | Clay_RenderCommandArray renderCommands = ClayVideoDemoPlaydate_CreateLayout(selectedDocumentIndex); 112 | Clay_Playdate_Render(pd, renderCommands, textUserData.font); 113 | 114 | return 1; 115 | } 116 | -------------------------------------------------------------------------------- /examples/raylib-multi-context/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_raylib_multi_context C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | # Adding Raylib 6 | include(FetchContent) 7 | set(FETCHCONTENT_QUIET FALSE) 8 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 9 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 10 | 11 | FetchContent_Declare( 12 | raylib 13 | GIT_REPOSITORY "https://github.com/raysan5/raylib.git" 14 | GIT_TAG "5.5" 15 | GIT_PROGRESS TRUE 16 | GIT_SHALLOW TRUE 17 | ) 18 | 19 | FetchContent_MakeAvailable(raylib) 20 | 21 | add_executable(clay_examples_raylib_multi_context main.c) 22 | 23 | target_compile_options(clay_examples_raylib_multi_context PUBLIC) 24 | target_include_directories(clay_examples_raylib_multi_context PUBLIC .) 25 | 26 | target_link_libraries(clay_examples_raylib_multi_context PUBLIC raylib) 27 | 28 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 29 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 30 | 31 | add_custom_command( 32 | TARGET clay_examples_raylib_multi_context POST_BUILD 33 | COMMAND ${CMAKE_COMMAND} -E copy_directory 34 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 35 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 36 | -------------------------------------------------------------------------------- /examples/raylib-multi-context/main.c: -------------------------------------------------------------------------------- 1 | #define CLAY_IMPLEMENTATION 2 | #include "../../clay.h" 3 | #include "../../renderers/raylib/clay_renderer_raylib.c" 4 | #include "../shared-layouts/clay-video-demo.c" 5 | 6 | void HandleClayErrors(Clay_ErrorData errorData) { 7 | printf("%s", errorData.errorText.chars); 8 | } 9 | 10 | Clay_RenderCommandArray CreateLayout(Clay_Context* context, ClayVideoDemo_Data *data) { 11 | Clay_SetCurrentContext(context); 12 | Clay_SetDebugModeEnabled(true); 13 | // Run once per frame 14 | Clay_SetLayoutDimensions((Clay_Dimensions) { 15 | .width = GetScreenWidth(), 16 | .height = GetScreenHeight() / 2 17 | }); 18 | Vector2 mousePosition = GetMousePosition(); 19 | mousePosition.y -= data->yOffset; 20 | Vector2 scrollDelta = GetMouseWheelMoveV(); 21 | Clay_SetPointerState( 22 | (Clay_Vector2) { mousePosition.x, mousePosition.y }, 23 | IsMouseButtonDown(0) 24 | ); 25 | Clay_UpdateScrollContainers( 26 | true, 27 | (Clay_Vector2) { scrollDelta.x, scrollDelta.y }, 28 | GetFrameTime() 29 | ); 30 | return ClayVideoDemo_CreateLayout(data); 31 | } 32 | 33 | int main(void) { 34 | documents.documents = (Document[]) { 35 | { .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") }, 36 | { .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.") }, 37 | { .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.") }, 38 | { .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") }, 39 | { .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") }, 40 | }; 41 | 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 42 | 43 | Font fonts[1]; 44 | fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400); 45 | SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR); 46 | 47 | uint64_t clayRequiredMemory = Clay_MinMemorySize(); 48 | 49 | Clay_Arena clayMemoryTop = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); 50 | Clay_Context *clayContextTop = Clay_Initialize(clayMemoryTop, (Clay_Dimensions) { 51 | .width = GetScreenWidth(), 52 | .height = GetScreenHeight() / 2 53 | }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published 54 | ClayVideoDemo_Data dataTop = ClayVideoDemo_Initialize(); 55 | Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); 56 | 57 | Clay_Arena clayMemoryBottom = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); 58 | Clay_Context *clayContextBottom = Clay_Initialize(clayMemoryBottom, (Clay_Dimensions) { 59 | .width = GetScreenWidth(), 60 | .height = GetScreenHeight() / 2 61 | }, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published 62 | ClayVideoDemo_Data dataBottom = ClayVideoDemo_Initialize(); 63 | Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts); 64 | 65 | while (!WindowShouldClose()) { 66 | dataBottom.yOffset = GetScreenHeight() / 2; 67 | Clay_RenderCommandArray renderCommandsTop = CreateLayout(clayContextTop, &dataTop); 68 | Clay_RenderCommandArray renderCommandsBottom = CreateLayout(clayContextBottom, &dataBottom); 69 | BeginDrawing(); 70 | ClearBackground(BLACK); 71 | Clay_Raylib_Render(renderCommandsTop, fonts); 72 | Clay_Raylib_Render(renderCommandsBottom, fonts); 73 | EndDrawing(); 74 | } 75 | 76 | Clay_Raylib_Close(); 77 | } 78 | -------------------------------------------------------------------------------- /examples/raylib-multi-context/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-multi-context/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/raylib-multi-context/resources/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-multi-context/resources/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /examples/raylib-multi-context/resources/profile-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-multi-context/resources/profile-picture.png -------------------------------------------------------------------------------- /examples/raylib-sidebar-scrolling-container/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_raylib_sidebar_scrolling_container C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | # Adding Raylib 6 | include(FetchContent) 7 | set(FETCHCONTENT_QUIET FALSE) 8 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples 9 | set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games 10 | 11 | FetchContent_Declare( 12 | raylib 13 | GIT_REPOSITORY "https://github.com/raysan5/raylib.git" 14 | GIT_TAG "5.5" 15 | GIT_PROGRESS TRUE 16 | GIT_SHALLOW TRUE 17 | ) 18 | 19 | FetchContent_MakeAvailable(raylib) 20 | 21 | add_executable(clay_examples_raylib_sidebar_scrolling_container main.c multi-compilation-unit.c) 22 | 23 | target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC) 24 | target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .) 25 | 26 | target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib) 27 | if(MSVC) 28 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 29 | else() 30 | set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") 31 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") 32 | endif() 33 | 34 | add_custom_command( 35 | TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD 36 | COMMAND ${CMAKE_COMMAND} -E copy_directory 37 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 38 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 39 | -------------------------------------------------------------------------------- /examples/raylib-sidebar-scrolling-container/multi-compilation-unit.c: -------------------------------------------------------------------------------- 1 | #include "../../clay.h" 2 | 3 | // NOTE: This file only exists to make sure that clay works when included in multiple translation units. 4 | 5 | void SatisfyCompiler(void) { 6 | CLAY({ .id = CLAY_ID("SatisfyCompiler") }) { 7 | CLAY_TEXT(CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24 })); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/raylib-sidebar-scrolling-container/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-sidebar-scrolling-container/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/raylib-sidebar-scrolling-container/resources/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-sidebar-scrolling-container/resources/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /examples/raylib-sidebar-scrolling-container/resources/profile-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/raylib-sidebar-scrolling-container/resources/profile-picture.png -------------------------------------------------------------------------------- /examples/shared-layouts/clay-video-demo.c: -------------------------------------------------------------------------------- 1 | #include "../../clay.h" 2 | #include 3 | 4 | const int FONT_ID_BODY_16 = 0; 5 | Clay_Color COLOR_WHITE = { 255, 255, 255, 255}; 6 | 7 | void RenderHeaderButton(Clay_String text) { 8 | CLAY({ 9 | .layout = { .padding = { 16, 16, 8, 8 }}, 10 | .backgroundColor = { 140, 140, 140, 255 }, 11 | .cornerRadius = CLAY_CORNER_RADIUS(5) 12 | }) { 13 | CLAY_TEXT(text, CLAY_TEXT_CONFIG({ 14 | .fontId = FONT_ID_BODY_16, 15 | .fontSize = 16, 16 | .textColor = { 255, 255, 255, 255 } 17 | })); 18 | } 19 | } 20 | 21 | void RenderDropdownMenuItem(Clay_String text) { 22 | CLAY({.layout = { .padding = CLAY_PADDING_ALL(16)}}) { 23 | CLAY_TEXT(text, CLAY_TEXT_CONFIG({ 24 | .fontId = FONT_ID_BODY_16, 25 | .fontSize = 16, 26 | .textColor = { 255, 255, 255, 255 } 27 | })); 28 | } 29 | } 30 | 31 | typedef struct { 32 | Clay_String title; 33 | Clay_String contents; 34 | } Document; 35 | 36 | typedef struct { 37 | Document *documents; 38 | uint32_t length; 39 | } DocumentArray; 40 | 41 | Document documentsRaw[5]; 42 | 43 | DocumentArray documents = { 44 | .length = 5, 45 | .documents = documentsRaw 46 | }; 47 | 48 | typedef struct { 49 | intptr_t offset; 50 | intptr_t memory; 51 | } ClayVideoDemo_Arena; 52 | 53 | typedef struct { 54 | int32_t selectedDocumentIndex; 55 | float yOffset; 56 | ClayVideoDemo_Arena frameArena; 57 | } ClayVideoDemo_Data; 58 | 59 | typedef struct { 60 | int32_t requestedDocumentIndex; 61 | int32_t* selectedDocumentIndex; 62 | } SidebarClickData; 63 | 64 | void HandleSidebarInteraction( 65 | Clay_ElementId elementId, 66 | Clay_PointerData pointerData, 67 | intptr_t userData 68 | ) { 69 | SidebarClickData *clickData = (SidebarClickData*)userData; 70 | // If this button was clicked 71 | if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 72 | if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) { 73 | // Select the corresponding document 74 | *clickData->selectedDocumentIndex = clickData->requestedDocumentIndex; 75 | } 76 | } 77 | } 78 | 79 | ClayVideoDemo_Data ClayVideoDemo_Initialize() { 80 | 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") }; 81 | 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.") }; 82 | 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.") }; 83 | documents.documents[3] = (Document){ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") }; 84 | documents.documents[4] = (Document){ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") }; 85 | 86 | ClayVideoDemo_Data data = { 87 | .frameArena = { .memory = (intptr_t)malloc(1024) } 88 | }; 89 | return data; 90 | } 91 | 92 | Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) { 93 | data->frameArena.offset = 0; 94 | 95 | Clay_BeginLayout(); 96 | 97 | Clay_Sizing layoutExpand = { 98 | .width = CLAY_SIZING_GROW(0), 99 | .height = CLAY_SIZING_GROW(0) 100 | }; 101 | 102 | Clay_Color contentBackgroundColor = { 90, 90, 90, 255 }; 103 | 104 | // Build UI here 105 | CLAY({ .id = CLAY_ID("OuterContainer"), 106 | .backgroundColor = {43, 41, 51, 255 }, 107 | .layout = { 108 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 109 | .sizing = layoutExpand, 110 | .padding = CLAY_PADDING_ALL(16), 111 | .childGap = 16 112 | } 113 | }) { 114 | // Child elements go inside braces 115 | CLAY({ .id = CLAY_ID("HeaderBar"), 116 | .layout = { 117 | .sizing = { 118 | .height = CLAY_SIZING_FIXED(60), 119 | .width = CLAY_SIZING_GROW(0) 120 | }, 121 | .padding = { 16, 16, 0, 0 }, 122 | .childGap = 16, 123 | .childAlignment = { 124 | .y = CLAY_ALIGN_Y_CENTER 125 | } 126 | }, 127 | .backgroundColor = contentBackgroundColor, 128 | .cornerRadius = CLAY_CORNER_RADIUS(8) 129 | }) { 130 | // Header buttons go here 131 | CLAY({ .id = CLAY_ID("FileButton"), 132 | .layout = { .padding = { 16, 16, 8, 8 }}, 133 | .backgroundColor = {140, 140, 140, 255 }, 134 | .cornerRadius = CLAY_CORNER_RADIUS(5) 135 | }) { 136 | CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({ 137 | .fontId = FONT_ID_BODY_16, 138 | .fontSize = 16, 139 | .textColor = { 255, 255, 255, 255 } 140 | })); 141 | 142 | bool fileMenuVisible = 143 | Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton"))) 144 | || 145 | Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu"))); 146 | 147 | if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap 148 | CLAY({ .id = CLAY_ID("FileMenu"), 149 | .floating = { 150 | .attachTo = CLAY_ATTACH_TO_PARENT, 151 | .attachPoints = { 152 | .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM 153 | }, 154 | }, 155 | .layout = { 156 | .padding = {0, 0, 8, 8 } 157 | } 158 | }) { 159 | CLAY({ 160 | .layout = { 161 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 162 | .sizing = { 163 | .width = CLAY_SIZING_FIXED(200) 164 | }, 165 | }, 166 | .backgroundColor = {40, 40, 40, 255 }, 167 | .cornerRadius = CLAY_CORNER_RADIUS(8) 168 | }) { 169 | // Render dropdown items here 170 | RenderDropdownMenuItem(CLAY_STRING("New")); 171 | RenderDropdownMenuItem(CLAY_STRING("Open")); 172 | RenderDropdownMenuItem(CLAY_STRING("Close")); 173 | } 174 | } 175 | } 176 | } 177 | RenderHeaderButton(CLAY_STRING("Edit")); 178 | CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {} 179 | RenderHeaderButton(CLAY_STRING("Upload")); 180 | RenderHeaderButton(CLAY_STRING("Media")); 181 | RenderHeaderButton(CLAY_STRING("Support")); 182 | } 183 | 184 | CLAY({ 185 | .id = CLAY_ID("LowerContent"), 186 | .layout = { .sizing = layoutExpand, .childGap = 16 } 187 | }) { 188 | CLAY({ 189 | .id = CLAY_ID("Sidebar"), 190 | .backgroundColor = contentBackgroundColor, 191 | .layout = { 192 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 193 | .padding = CLAY_PADDING_ALL(16), 194 | .childGap = 8, 195 | .sizing = { 196 | .width = CLAY_SIZING_FIXED(250), 197 | .height = CLAY_SIZING_GROW(0) 198 | } 199 | } 200 | }) { 201 | for (int i = 0; i < documents.length; i++) { 202 | Document document = documents.documents[i]; 203 | Clay_LayoutConfig sidebarButtonLayout = { 204 | .sizing = { .width = CLAY_SIZING_GROW(0) }, 205 | .padding = CLAY_PADDING_ALL(16) 206 | }; 207 | 208 | if (i == data->selectedDocumentIndex) { 209 | CLAY({ 210 | .layout = sidebarButtonLayout, 211 | .backgroundColor = {120, 120, 120, 255 }, 212 | .cornerRadius = CLAY_CORNER_RADIUS(8) 213 | }) { 214 | CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ 215 | .fontId = FONT_ID_BODY_16, 216 | .fontSize = 20, 217 | .textColor = { 255, 255, 255, 255 } 218 | })); 219 | } 220 | } else { 221 | SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset); 222 | *clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex }; 223 | data->frameArena.offset += sizeof(SidebarClickData); 224 | CLAY({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) { 225 | Clay_OnHover(HandleSidebarInteraction, (intptr_t)clickData); 226 | CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({ 227 | .fontId = FONT_ID_BODY_16, 228 | .fontSize = 20, 229 | .textColor = { 255, 255, 255, 255 } 230 | })); 231 | } 232 | } 233 | } 234 | } 235 | 236 | CLAY({ .id = CLAY_ID("MainContent"), 237 | .backgroundColor = contentBackgroundColor, 238 | .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, 239 | .layout = { 240 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 241 | .childGap = 16, 242 | .padding = CLAY_PADDING_ALL(16), 243 | .sizing = layoutExpand 244 | } 245 | }) { 246 | Document selectedDocument = documents.documents[data->selectedDocumentIndex]; 247 | CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({ 248 | .fontId = FONT_ID_BODY_16, 249 | .fontSize = 24, 250 | .textColor = COLOR_WHITE 251 | })); 252 | CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({ 253 | .fontId = FONT_ID_BODY_16, 254 | .fontSize = 24, 255 | .textColor = COLOR_WHITE 256 | })); 257 | } 258 | } 259 | } 260 | 261 | Clay_RenderCommandArray renderCommands = Clay_EndLayout(); 262 | for (int32_t i = 0; i < renderCommands.length; i++) { 263 | Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset; 264 | } 265 | return renderCommands; 266 | } -------------------------------------------------------------------------------- /examples/sokol-corner-radius/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(sokol_corner_radius C) 3 | 4 | if(CMAKE_SYSTEM_NAME STREQUAL Windows) 5 | add_executable(sokol_corner_radius WIN32 main.c) 6 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_corner_radius) 7 | else() 8 | add_executable(sokol_corner_radius main.c) 9 | endif() 10 | target_link_libraries(sokol_corner_radius PUBLIC sokol) -------------------------------------------------------------------------------- /examples/sokol-corner-radius/main.c: -------------------------------------------------------------------------------- 1 | #include "sokol_app.h" 2 | #include "sokol_gfx.h" 3 | #include "sokol_glue.h" 4 | #include "sokol_log.h" 5 | 6 | #define CLAY_IMPLEMENTATION 7 | #include "../../clay.h" 8 | 9 | #include "util/sokol_gl.h" 10 | #include "fontstash.h" 11 | #include "util/sokol_fontstash.h" 12 | #define SOKOL_CLAY_IMPL 13 | #include "../../renderers/sokol/sokol_clay.h" 14 | 15 | static void init() { 16 | sg_setup(&(sg_desc){ 17 | .environment = sglue_environment(), 18 | .logger.func = slog_func, 19 | }); 20 | sgl_setup(&(sgl_desc_t){ 21 | .logger.func = slog_func, 22 | }); 23 | sclay_setup(); 24 | uint64_t totalMemorySize = Clay_MinMemorySize(); 25 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); 26 | Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0}); 27 | Clay_SetMeasureTextFunction(sclay_measure_text, NULL); 28 | } 29 | 30 | Clay_RenderCommandArray CornerRadiusTest(){ 31 | Clay_BeginLayout(); 32 | Clay_Sizing layoutExpand = { 33 | .width = CLAY_SIZING_GROW(0), 34 | .height = CLAY_SIZING_GROW(0) 35 | }; 36 | CLAY({ .id = CLAY_ID("OuterContainer"), 37 | .backgroundColor = {43, 41, 51, 255}, 38 | .layout = { 39 | .layoutDirection = CLAY_TOP_TO_BOTTOM, 40 | .sizing = layoutExpand, 41 | .padding = {0, 0, 20, 20}, 42 | .childGap = 20 43 | } 44 | }) { 45 | for(int i = 0; i < 6; ++i){ 46 | CLAY({ .id = CLAY_IDI("Row", i), 47 | .layout = { 48 | .layoutDirection = CLAY_LEFT_TO_RIGHT, 49 | .sizing = layoutExpand, 50 | .padding = {20, 20, 0, 0}, 51 | .childGap = 20 52 | } 53 | }) { 54 | for(int j = 0; j < 6; ++j){ 55 | CLAY({ .id = CLAY_IDI("Tile", i*6+j), 56 | .backgroundColor = {120, 140, 255, 128}, 57 | .cornerRadius = {(i%3)*15, (j%3)*15, (i/2)*15, (j/2)*15}, 58 | .border = { 59 | .color = {120, 140, 255, 255}, 60 | .width = {3, 9, 6, 12, 0}, 61 | }, 62 | .layout = { .sizing = layoutExpand } 63 | }); 64 | } 65 | } 66 | } 67 | } 68 | return Clay_EndLayout(); 69 | } 70 | 71 | static void frame() { 72 | sclay_new_frame(); 73 | Clay_RenderCommandArray renderCommands = CornerRadiusTest(); 74 | 75 | sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); 76 | sgl_matrix_mode_modelview(); 77 | sgl_load_identity(); 78 | sclay_render(renderCommands, NULL); 79 | sgl_draw(); 80 | sg_end_pass(); 81 | sg_commit(); 82 | } 83 | 84 | static void event(const sapp_event *ev) { 85 | if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){ 86 | Clay_SetDebugModeEnabled(true); 87 | } else { 88 | sclay_handle_event(ev); 89 | } 90 | } 91 | 92 | static void cleanup() { 93 | sclay_shutdown(); 94 | sgl_shutdown(); 95 | sg_shutdown(); 96 | } 97 | 98 | sapp_desc sokol_main(int argc, char **argv) { 99 | return (sapp_desc){ 100 | .init_cb = init, 101 | .frame_cb = frame, 102 | .event_cb = event, 103 | .cleanup_cb = cleanup, 104 | .window_title = "Clay - Corner Radius Test", 105 | .width = 800, 106 | .height = 600, 107 | .icon.sokol_default = true, 108 | .logger.func = slog_func, 109 | }; 110 | } 111 | -------------------------------------------------------------------------------- /examples/sokol-video-demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(sokol_video_demo C) 3 | 4 | include(FetchContent) 5 | set(FETCHCONTENT_QUIET FALSE) 6 | 7 | # Linux -pthread shenanigans 8 | if (CMAKE_SYSTEM_NAME STREQUAL Linux) 9 | set(THREADS_PREFER_PTHREAD_FLAG ON) 10 | find_package(Threads REQUIRED) 11 | endif() 12 | 13 | FetchContent_Declare( 14 | fontstash 15 | GIT_REPOSITORY "https://github.com/memononen/fontstash.git" 16 | GIT_TAG "b5ddc9741061343740d85d636d782ed3e07cf7be" 17 | GIT_PROGRESS TRUE 18 | GIT_SHALLOW TRUE 19 | ) 20 | FetchContent_MakeAvailable(fontstash) 21 | 22 | FetchContent_Declare( 23 | sokol 24 | GIT_REPOSITORY "https://github.com/floooh/sokol.git" 25 | GIT_TAG "master" 26 | GIT_PROGRESS TRUE 27 | GIT_SHALLOW TRUE 28 | ) 29 | FetchContent_MakeAvailable(sokol) 30 | set(sokol_HEADERS 31 | ${sokol_SOURCE_DIR}/sokol_app.h 32 | ${sokol_SOURCE_DIR}/sokol_gfx.h 33 | ${sokol_SOURCE_DIR}/sokol_glue.h 34 | ${sokol_SOURCE_DIR}/sokol_log.h 35 | ${sokol_SOURCE_DIR}/util/sokol_gl.h 36 | ${fontstash_SOURCE_DIR}/src/fontstash.h 37 | ${sokol_SOURCE_DIR}/util/sokol_fontstash.h) 38 | if(CMAKE_SYSTEM_NAME STREQUAL Darwin) 39 | add_library(sokol STATIC sokol.c ${sokol_HEADERS}) 40 | target_compile_options(sokol PRIVATE -x objective-c) 41 | target_link_libraries(sokol PUBLIC 42 | "-framework QuartzCore" 43 | "-framework Cocoa" 44 | "-framework MetalKit" 45 | "-framework Metal") 46 | else() 47 | add_library(sokol STATIC sokol.c ${sokol_HEADERS}) 48 | if (CMAKE_SYSTEM_NAME STREQUAL Linux) 49 | target_compile_definitions(sokol PRIVATE SOKOL_GLCORE=1) 50 | target_link_libraries(sokol INTERFACE X11 Xi Xcursor GL dl m) 51 | target_link_libraries(sokol PUBLIC Threads::Threads) 52 | endif() 53 | endif() 54 | target_include_directories(sokol INTERFACE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src 55 | PRIVATE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src) 56 | 57 | 58 | 59 | if(CMAKE_SYSTEM_NAME STREQUAL Windows) 60 | add_executable(sokol_video_demo WIN32 main.c) 61 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_video_demo) 62 | else() 63 | add_executable(sokol_video_demo main.c) 64 | endif() 65 | target_link_libraries(sokol_video_demo PUBLIC sokol) 66 | 67 | add_custom_command( 68 | TARGET sokol_video_demo POST_BUILD 69 | COMMAND ${CMAKE_COMMAND} -E copy_directory 70 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 71 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 72 | -------------------------------------------------------------------------------- /examples/sokol-video-demo/main.c: -------------------------------------------------------------------------------- 1 | #include "sokol_app.h" 2 | #include "sokol_gfx.h" 3 | #include "sokol_glue.h" 4 | #include "sokol_log.h" 5 | 6 | #define CLAY_IMPLEMENTATION 7 | #include "../../clay.h" 8 | 9 | #include "util/sokol_gl.h" 10 | #include "fontstash.h" 11 | #include "util/sokol_fontstash.h" 12 | #define SOKOL_CLAY_IMPL 13 | #include "../../renderers/sokol/sokol_clay.h" 14 | 15 | #include "../shared-layouts/clay-video-demo.c" 16 | 17 | static ClayVideoDemo_Data demoData; 18 | static sclay_font_t fonts[1]; 19 | 20 | static void init() { 21 | sg_setup(&(sg_desc){ 22 | .environment = sglue_environment(), 23 | .logger.func = slog_func, 24 | }); 25 | sgl_setup(&(sgl_desc_t){ 26 | .logger.func = slog_func, 27 | }); 28 | sclay_setup(); 29 | uint64_t totalMemorySize = Clay_MinMemorySize(); 30 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); 31 | Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0}); 32 | fonts[FONT_ID_BODY_16] = sclay_add_font("resources/Roboto-Regular.ttf"); 33 | Clay_SetMeasureTextFunction(sclay_measure_text, &fonts); 34 | demoData = ClayVideoDemo_Initialize(); 35 | } 36 | 37 | static void frame() { 38 | sclay_new_frame(); 39 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); 40 | 41 | sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() }); 42 | sgl_matrix_mode_modelview(); 43 | sgl_load_identity(); 44 | sclay_render(renderCommands, fonts); 45 | sgl_draw(); 46 | sg_end_pass(); 47 | sg_commit(); 48 | } 49 | 50 | static void event(const sapp_event *ev) { 51 | if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){ 52 | Clay_SetDebugModeEnabled(true); 53 | } else { 54 | sclay_handle_event(ev); 55 | } 56 | } 57 | 58 | static void cleanup() { 59 | sclay_shutdown(); 60 | sgl_shutdown(); 61 | sg_shutdown(); 62 | } 63 | 64 | sapp_desc sokol_main(int argc, char **argv) { 65 | return (sapp_desc){ 66 | .init_cb = init, 67 | .frame_cb = frame, 68 | .event_cb = event, 69 | .cleanup_cb = cleanup, 70 | .window_title = "Clay - Sokol Renderer Example", 71 | .width = 800, 72 | .height = 600, 73 | .high_dpi = true, 74 | .icon.sokol_default = true, 75 | .logger.func = slog_func, 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /examples/sokol-video-demo/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/sokol-video-demo/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /examples/sokol-video-demo/sokol.c: -------------------------------------------------------------------------------- 1 | #define SOKOL_IMPL 2 | #if defined(_WIN32) 3 | #define SOKOL_D3D11 4 | #elif defined(__EMSCRIPTEN__) 5 | #define SOKOL_GLES2 6 | #elif defined(__APPLE__) 7 | #define SOKOL_METAL 8 | #else 9 | #define SOKOL_GLCORE33 10 | #endif 11 | #include "sokol_app.h" 12 | #include "sokol_gfx.h" 13 | #include "sokol_time.h" 14 | #include "sokol_fetch.h" 15 | #include "sokol_glue.h" 16 | #include "sokol_log.h" 17 | 18 | #include "util/sokol_gl.h" 19 | #include // fontstash requires this 20 | #include // fontstash requires this 21 | #define FONTSTASH_IMPLEMENTATION 22 | #include "fontstash.h" 23 | #define SOKOL_FONTSTASH_IMPL 24 | #include "util/sokol_fontstash.h" 25 | -------------------------------------------------------------------------------- /examples/terminal-example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(clay_examples_terminal C) 3 | set(CMAKE_C_STANDARD 99) 4 | 5 | add_executable(clay_examples_terminal main.c) 6 | 7 | target_compile_options(clay_examples_terminal PUBLIC) 8 | target_include_directories(clay_examples_terminal PUBLIC .) 9 | if (CMAKE_SYSTEM_NAME STREQUAL Linux) 10 | target_link_libraries(clay_examples_terminal INTERFACE m) 11 | endif() 12 | 13 | target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY}) 14 | set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") 15 | set(CMAKE_CXX_FLAGS_RELEASE "-O3") -------------------------------------------------------------------------------- /examples/terminal-example/main.c: -------------------------------------------------------------------------------- 1 | // Must be defined in one file, _before_ #include "clay.h" 2 | #define CLAY_IMPLEMENTATION 3 | 4 | #include 5 | #include "../../clay.h" 6 | #include "../../renderers/terminal/clay_renderer_terminal_ansi.c" 7 | #include "../shared-layouts/clay-video-demo.c" 8 | 9 | const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; 10 | const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; 11 | const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; 12 | 13 | void HandleClayErrors(Clay_ErrorData errorData) { 14 | printf("%s", errorData.errorText.chars); 15 | } 16 | 17 | int main() { 18 | const int width = 145; 19 | const int height = 41; 20 | int columnWidth = 16; 21 | 22 | uint64_t totalMemorySize = Clay_MinMemorySize(); 23 | Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); 24 | Clay_Initialize(arena, 25 | (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, 26 | (Clay_ErrorHandler) {HandleClayErrors}); 27 | // Tell clay how to measure text 28 | Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); 29 | ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); 30 | 31 | while (true) { 32 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); 33 | 34 | Clay_Terminal_Render(renderCommands, width, height, columnWidth); 35 | 36 | fflush(stdout); 37 | sleep(1); 38 | } 39 | } -------------------------------------------------------------------------------- /examples/win32_gdi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.27) 2 | project(win32_gdi C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | 6 | add_executable(win32_gdi WIN32 main.c) 7 | 8 | target_compile_options(win32_gdi PUBLIC) 9 | target_include_directories(win32_gdi PUBLIC .) 10 | 11 | add_custom_command( 12 | TARGET win32_gdi POST_BUILD 13 | COMMAND ${CMAKE_COMMAND} -E copy_directory 14 | ${CMAKE_CURRENT_SOURCE_DIR}/resources 15 | ${CMAKE_CURRENT_BINARY_DIR}/resources) 16 | -------------------------------------------------------------------------------- /examples/win32_gdi/build.ps1: -------------------------------------------------------------------------------- 1 | 2 | # to build this, install mingw 3 | 4 | gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output -------------------------------------------------------------------------------- /examples/win32_gdi/main.c: -------------------------------------------------------------------------------- 1 | 2 | #define WIN32_LEAN_AND_MEAN 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "../../renderers/win32_gdi/clay_renderer_gdi.c" 12 | 13 | #define CLAY_IMPLEMENTATION 14 | #include "../../clay.h" 15 | 16 | #include "../shared-layouts/clay-video-demo.c" 17 | 18 | ClayVideoDemo_Data demo_data; 19 | 20 | #define APPNAME "Clay GDI Example" 21 | char szAppName[] = APPNAME; // The name of this application 22 | char szTitle[] = APPNAME; // The title bar text 23 | 24 | void CenterWindow(HWND hWnd); 25 | 26 | long lastMsgTime = 0; 27 | bool ui_debug_mode; 28 | HFONT fonts[1]; 29 | 30 | #ifndef RECTWIDTH 31 | #define RECTWIDTH(rc) ((rc).right - (rc).left) 32 | #endif 33 | #ifndef RECTHEIGHT 34 | #define RECTHEIGHT(rc) ((rc).bottom - (rc).top) 35 | #endif 36 | 37 | LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 38 | { 39 | 40 | switch (message) 41 | { 42 | 43 | // ----------------------- first and last 44 | case WM_CREATE: 45 | CenterWindow(hwnd); 46 | break; 47 | 48 | case WM_DESTROY: 49 | PostQuitMessage(0); 50 | break; 51 | 52 | case WM_MOUSEWHEEL: // scrolling data 53 | { 54 | short zDelta = GET_WHEEL_DELTA_WPARAM(wParam); 55 | // todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed 56 | long now = GetMessageTime(); 57 | float dt = (now - lastMsgTime) / 1000.00; 58 | 59 | lastMsgTime = now; 60 | 61 | // little hacky hack to make scrolling *feel* right 62 | if (abs(zDelta) > 100) 63 | { 64 | zDelta = zDelta * .012; 65 | } 66 | 67 | Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt); 68 | 69 | InvalidateRect(hwnd, NULL, false); // force a wm_paint event 70 | break; 71 | } 72 | case WM_RBUTTONUP: 73 | case WM_LBUTTONUP: 74 | case WM_LBUTTONDOWN: 75 | case WM_RBUTTONDOWN: 76 | case WM_MOUSEMOVE: // mouse events 77 | { 78 | short mouseX = GET_X_LPARAM(lParam); 79 | short mouseY = GET_Y_LPARAM(lParam); 80 | short mouseButtons = LOWORD(wParam); 81 | 82 | Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01); 83 | 84 | InvalidateRect(hwnd, NULL, false); // force a wm_paint event 85 | break; 86 | } 87 | 88 | case WM_SIZE: // resize events 89 | { 90 | 91 | RECT r = {0}; 92 | if (GetClientRect(hwnd, &r)) 93 | { 94 | Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left}; 95 | Clay_SetLayoutDimensions(dim); 96 | } 97 | 98 | InvalidateRect(hwnd, NULL, false); // force a wm_paint event 99 | 100 | break; 101 | } 102 | 103 | case WM_KEYDOWN: 104 | if (VK_ESCAPE == wParam) 105 | { 106 | DestroyWindow(hwnd); 107 | break; 108 | } 109 | 110 | if (wParam == VK_F12) 111 | { 112 | Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode); 113 | break; 114 | } 115 | 116 | printf("Key Pressed: %d\r\n", wParam); 117 | InvalidateRect(hwnd, NULL, false); // force a wm_paint event 118 | break; 119 | 120 | // ----------------------- render 121 | case WM_PAINT: 122 | { 123 | Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data); 124 | Clay_Win32_Render(hwnd, renderCommands, fonts); 125 | break; 126 | } 127 | 128 | // ----------------------- let windows do all other stuff 129 | default: 130 | return DefWindowProc(hwnd, message, wParam, lParam); 131 | } 132 | return 0; 133 | } 134 | 135 | bool didAllocConsole = false; 136 | 137 | void HandleClayErrors(Clay_ErrorData errorData) 138 | { 139 | if (!didAllocConsole) 140 | { 141 | didAllocConsole = AllocConsole(); 142 | } 143 | 144 | printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars); 145 | } 146 | 147 | int APIENTRY WinMain( 148 | HINSTANCE hInstance, 149 | HINSTANCE hPrevInstance, 150 | LPSTR lpCmdLine, 151 | int nCmdShow) 152 | { 153 | MSG msg; 154 | WNDCLASS wc; 155 | HWND hwnd; 156 | 157 | demo_data = ClayVideoDemo_Initialize(); 158 | 159 | uint64_t clayRequiredMemory = Clay_MinMemorySize(); 160 | Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); 161 | Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published 162 | 163 | Clay_Win32_SetRendererFlags(CLAYGDI_RF_ALPHABLEND | CLAYGDI_RF_SMOOTHCORNERS); 164 | 165 | // Initialize clay fonts and text drawing 166 | fonts[FONT_ID_BODY_16] = Clay_Win32_SimpleCreateFont("resources/Roboto-Regular.ttf", "Roboto", -11, FW_NORMAL); 167 | Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, fonts); 168 | 169 | ZeroMemory(&wc, sizeof wc); 170 | wc.hInstance = hInstance; 171 | wc.lpszClassName = szAppName; 172 | wc.lpfnWndProc = (WNDPROC)WndProc; 173 | wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW; 174 | wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 175 | wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 176 | wc.hCursor = LoadCursor(NULL, IDC_ARROW); 177 | 178 | if (FALSE == RegisterClass(&wc)) 179 | return 0; 180 | 181 | // Calculate window rectangle by given client size 182 | // TODO: AdjustWindowRectExForDpi for DPI support 183 | RECT rcWindow = { .right = 800, .bottom = 600 }; 184 | AdjustWindowRect(&rcWindow, WS_OVERLAPPEDWINDOW, FALSE); 185 | 186 | hwnd = CreateWindow( 187 | szAppName, 188 | szTitle, 189 | WS_OVERLAPPEDWINDOW | WS_VISIBLE, 190 | CW_USEDEFAULT, 191 | CW_USEDEFAULT, 192 | RECTWIDTH(rcWindow), // CW_USEDEFAULT, 193 | RECTHEIGHT(rcWindow), // CW_USEDEFAULT, 194 | 0, 195 | 0, 196 | hInstance, 197 | 0); 198 | 199 | if (hwnd == NULL) 200 | return 0; 201 | 202 | // Main message loop: 203 | while (GetMessage(&msg, NULL, 0, 0) > 0) 204 | { 205 | TranslateMessage(&msg); 206 | DispatchMessage(&msg); 207 | } 208 | 209 | return msg.wParam; 210 | } 211 | 212 | void CenterWindow(HWND hwnd_self) 213 | { 214 | HWND hwnd_parent; 215 | RECT rw_self, rc_parent, rw_parent; 216 | int xpos, ypos; 217 | 218 | hwnd_parent = GetParent(hwnd_self); 219 | if (NULL == hwnd_parent) 220 | hwnd_parent = GetDesktopWindow(); 221 | 222 | GetWindowRect(hwnd_parent, &rw_parent); 223 | GetClientRect(hwnd_parent, &rc_parent); 224 | GetWindowRect(hwnd_self, &rw_self); 225 | 226 | xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2; 227 | ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2; 228 | 229 | SetWindowPos( 230 | hwnd_self, NULL, 231 | xpos, ypos, 0, 0, 232 | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); 233 | } 234 | 235 | //+--------------------------------------------------------------------------- 236 | -------------------------------------------------------------------------------- /examples/win32_gdi/resources/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/examples/win32_gdi/resources/Roboto-Regular.ttf -------------------------------------------------------------------------------- /renderers/SDL2/README: -------------------------------------------------------------------------------- 1 | Note: on Mac OSX, SDL2 for some reason decides to automatically disable momentum scrolling on macbook trackpads. 2 | You can re enable it in objective C using: 3 | 4 | ```C 5 | [[NSUserDefaults standardUserDefaults] setBool: YES 6 | forKey: @"AppleMomentumScrollSupported"]; 7 | ``` 8 | -------------------------------------------------------------------------------- /renderers/SDL3/clay_renderer_SDL3.c: -------------------------------------------------------------------------------- 1 | #include "../../clay.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef struct { 8 | SDL_Renderer *renderer; 9 | TTF_TextEngine *textEngine; 10 | TTF_Font **fonts; 11 | } Clay_SDL3RendererData; 12 | 13 | /* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with 14 | * no AA or low resolution might make it appear as jagged curves) */ 15 | static int NUM_CIRCLE_SEGMENTS = 16; 16 | 17 | //all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles. 18 | static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) { 19 | const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 }; 20 | 21 | int indexCount = 0, vertexCount = 0; 22 | 23 | const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; 24 | const float clampedRadius = SDL_min(cornerRadius, minRadius); 25 | 26 | const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int) clampedRadius * 0.5f); 27 | 28 | int totalVertices = 4 + (4 * (numCircleSegments * 2)) + 2*4; 29 | int totalIndices = 6 + (4 * (numCircleSegments * 3)) + 6*4; 30 | 31 | SDL_Vertex vertices[totalVertices]; 32 | int indices[totalIndices]; 33 | 34 | //define center rectangle 35 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL 36 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR 37 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR 38 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL 39 | 40 | indices[indexCount++] = 0; 41 | indices[indexCount++] = 1; 42 | indices[indexCount++] = 3; 43 | indices[indexCount++] = 1; 44 | indices[indexCount++] = 2; 45 | indices[indexCount++] = 3; 46 | 47 | //define rounded corners as triangle fans 48 | const float step = (SDL_PI_F/2) / numCircleSegments; 49 | for (int i = 0; i < numCircleSegments; i++) { 50 | const float angle1 = (float)i * step; 51 | const float angle2 = ((float)i + 1.0f) * step; 52 | 53 | for (int j = 0; j < 4; j++) { // Iterate over four corners 54 | float cx, cy, signX, signY; 55 | 56 | switch (j) { 57 | case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left 58 | case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right 59 | case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right 60 | case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left 61 | default: return; 62 | } 63 | 64 | vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} }; 65 | vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} }; 66 | 67 | indices[indexCount++] = j; // Connect to corresponding central rectangle vertex 68 | indices[indexCount++] = vertexCount - 2; 69 | indices[indexCount++] = vertexCount - 1; 70 | } 71 | } 72 | 73 | //Define edge rectangles 74 | // Top edge 75 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL 76 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR 77 | 78 | indices[indexCount++] = 0; 79 | indices[indexCount++] = vertexCount - 2; //TL 80 | indices[indexCount++] = vertexCount - 1; //TR 81 | indices[indexCount++] = 1; 82 | indices[indexCount++] = 0; 83 | indices[indexCount++] = vertexCount - 1; //TR 84 | // Right edge 85 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT 86 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB 87 | 88 | indices[indexCount++] = 1; 89 | indices[indexCount++] = vertexCount - 2; //RT 90 | indices[indexCount++] = vertexCount - 1; //RB 91 | indices[indexCount++] = 2; 92 | indices[indexCount++] = 1; 93 | indices[indexCount++] = vertexCount - 1; //RB 94 | // Bottom edge 95 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR 96 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL 97 | 98 | indices[indexCount++] = 2; 99 | indices[indexCount++] = vertexCount - 2; //BR 100 | indices[indexCount++] = vertexCount - 1; //BL 101 | indices[indexCount++] = 3; 102 | indices[indexCount++] = 2; 103 | indices[indexCount++] = vertexCount - 1; //BL 104 | // Left edge 105 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB 106 | vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT 107 | 108 | indices[indexCount++] = 3; 109 | indices[indexCount++] = vertexCount - 2; //LB 110 | indices[indexCount++] = vertexCount - 1; //LT 111 | indices[indexCount++] = 0; 112 | indices[indexCount++] = 3; 113 | indices[indexCount++] = vertexCount - 1; //LT 114 | 115 | // Render everything 116 | SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount); 117 | } 118 | 119 | static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) { 120 | SDL_SetRenderDrawColor(rendererData->renderer, color.r, color.g, color.b, color.a); 121 | 122 | const float radStart = startAngle * (SDL_PI_F / 180.0f); 123 | const float radEnd = endAngle * (SDL_PI_F / 180.0f); 124 | 125 | const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)(radius * 1.5f)); //increase circle segments for larger circles, 1.5 is arbitrary. 126 | 127 | const float angleStep = (radEnd - radStart) / (float)numCircleSegments; 128 | const float thicknessStep = 0.4f; //arbitrary value to avoid overlapping lines. Changing THICKNESS_STEP or numCircleSegments might cause artifacts. 129 | 130 | for (float t = thicknessStep; t < thickness - thicknessStep; t += thicknessStep) { 131 | SDL_FPoint points[numCircleSegments + 1]; 132 | const float clampedRadius = SDL_max(radius - t, 1.0f); 133 | 134 | for (int i = 0; i <= numCircleSegments; i++) { 135 | const float angle = radStart + i * angleStep; 136 | points[i] = (SDL_FPoint){ 137 | SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius), 138 | SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) }; 139 | } 140 | SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1); 141 | } 142 | } 143 | 144 | SDL_Rect currentClippingRectangle; 145 | 146 | static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands) 147 | { 148 | for (size_t i = 0; i < rcommands->length; i++) { 149 | Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i); 150 | const Clay_BoundingBox bounding_box = rcmd->boundingBox; 151 | const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height }; 152 | 153 | switch (rcmd->commandType) { 154 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 155 | Clay_RectangleRenderData *config = &rcmd->renderData.rectangle; 156 | SDL_SetRenderDrawBlendMode(rendererData->renderer, SDL_BLENDMODE_BLEND); 157 | SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a); 158 | if (config->cornerRadius.topLeft > 0) { 159 | SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor); 160 | } else { 161 | SDL_RenderFillRect(rendererData->renderer, &rect); 162 | } 163 | } break; 164 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 165 | Clay_TextRenderData *config = &rcmd->renderData.text; 166 | TTF_Font *font = rendererData->fonts[config->fontId]; 167 | TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length); 168 | TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a); 169 | TTF_DrawRendererText(text, rect.x, rect.y); 170 | TTF_DestroyText(text); 171 | } break; 172 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 173 | Clay_BorderRenderData *config = &rcmd->renderData.border; 174 | 175 | const float minRadius = SDL_min(rect.w, rect.h) / 2.0f; 176 | const Clay_CornerRadius clampedRadii = { 177 | .topLeft = SDL_min(config->cornerRadius.topLeft, minRadius), 178 | .topRight = SDL_min(config->cornerRadius.topRight, minRadius), 179 | .bottomLeft = SDL_min(config->cornerRadius.bottomLeft, minRadius), 180 | .bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius) 181 | }; 182 | //edges 183 | SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); 184 | if (config->width.left > 0) { 185 | const float starting_y = rect.y + clampedRadii.topLeft; 186 | const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft; 187 | SDL_FRect line = { rect.x, starting_y, config->width.left, length }; 188 | SDL_RenderFillRect(rendererData->renderer, &line); 189 | } 190 | if (config->width.right > 0) { 191 | const float starting_x = rect.x + rect.w - (float)config->width.right; 192 | const float starting_y = rect.y + clampedRadii.topRight; 193 | const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight; 194 | SDL_FRect line = { starting_x, starting_y, config->width.right, length }; 195 | SDL_RenderFillRect(rendererData->renderer, &line); 196 | } 197 | if (config->width.top > 0) { 198 | const float starting_x = rect.x + clampedRadii.topLeft; 199 | const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight; 200 | SDL_FRect line = { starting_x, rect.y, length, config->width.top }; 201 | SDL_RenderFillRect(rendererData->renderer, &line); 202 | } 203 | if (config->width.bottom > 0) { 204 | const float starting_x = rect.x + clampedRadii.bottomLeft; 205 | const float starting_y = rect.y + rect.h - (float)config->width.bottom; 206 | const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight; 207 | SDL_FRect line = { starting_x, starting_y, length, config->width.bottom }; 208 | SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); 209 | SDL_RenderFillRect(rendererData->renderer, &line); 210 | } 211 | //corners 212 | if (config->cornerRadius.topLeft > 0) { 213 | const float centerX = rect.x + clampedRadii.topLeft -1; 214 | const float centerY = rect.y + clampedRadii.topLeft; 215 | SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft, 216 | 180.0f, 270.0f, config->width.top, config->color); 217 | } 218 | if (config->cornerRadius.topRight > 0) { 219 | const float centerX = rect.x + rect.w - clampedRadii.topRight -1; 220 | const float centerY = rect.y + clampedRadii.topRight; 221 | SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight, 222 | 270.0f, 360.0f, config->width.top, config->color); 223 | } 224 | if (config->cornerRadius.bottomLeft > 0) { 225 | const float centerX = rect.x + clampedRadii.bottomLeft -1; 226 | const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1; 227 | SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft, 228 | 90.0f, 180.0f, config->width.bottom, config->color); 229 | } 230 | if (config->cornerRadius.bottomRight > 0) { 231 | const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations??? 232 | const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1; 233 | SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight, 234 | 0.0f, 90.0f, config->width.bottom, config->color); 235 | } 236 | 237 | } break; 238 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { 239 | Clay_BoundingBox boundingBox = rcmd->boundingBox; 240 | currentClippingRectangle = (SDL_Rect) { 241 | .x = boundingBox.x, 242 | .y = boundingBox.y, 243 | .w = boundingBox.width, 244 | .h = boundingBox.height, 245 | }; 246 | SDL_SetRenderClipRect(rendererData->renderer, ¤tClippingRectangle); 247 | break; 248 | } 249 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { 250 | SDL_SetRenderClipRect(rendererData->renderer, NULL); 251 | break; 252 | } 253 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 254 | SDL_Texture *texture = (SDL_Texture *)rcmd->renderData.image.imageData; 255 | const SDL_FRect dest = { rect.x, rect.y, rect.w, rect.h }; 256 | SDL_RenderTexture(rendererData->renderer, texture, NULL, &dest); 257 | break; 258 | } 259 | default: 260 | SDL_Log("Unknown render command type: %d", rcmd->commandType); 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /renderers/cairo/clay_renderer_cairo.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024 Justin Andreas Lacoste (@27justin) 2 | // 3 | // This software is provided 'as-is', without any express or implied warranty. 4 | // In no event will the authors be held liable for any damages arising from the 5 | // use of this software. 6 | // 7 | // Permission is granted to anyone to use this software for any purpose, 8 | // including commercial applications, and to alter it and redistribute it 9 | // freely, subject to the following restrictions: 10 | // 11 | // 1. The origin of this software must not be misrepresented; you must not 12 | // claim that you wrote the original software. If you use this software in a 13 | // product, an acknowledgment in the product documentation would be 14 | // appreciated but is not required. 15 | // 16 | // 2. Altered source versions must be plainly marked as such, and must not 17 | // be misrepresented as being the original software. 18 | // 19 | // 3. This notice may not be removed or altered from any source 20 | // distribution. 21 | // 22 | // SPDX-License-Identifier: Zlib 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #define CLAY_IMPLEMENTATION 30 | #include "../../clay.h" 31 | 32 | #include 33 | 34 | //////////////////////////////// 35 | // 36 | // Public API 37 | // 38 | 39 | // Initialize the internal cairo pointer with the user provided instance. 40 | // This is REQUIRED before calling Clay_Cairo_Render. 41 | void Clay_Cairo_Initialize(cairo_t *cairo); 42 | 43 | // Render the command queue to the `cairo_t*` instance you called 44 | // `Clay_Cairo_Initialize` on. 45 | void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts); 46 | //////////////////////////////// 47 | 48 | 49 | //////////////////////////////// 50 | // Convencience macros 51 | // 52 | #define CLAY_TO_CAIRO(color) color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0 53 | #define DEG2RAD(degrees) (degrees * ( M_PI / 180.0 ) ) 54 | //////////////////////////////// 55 | 56 | 57 | //////////////////////////////// 58 | // Implementation 59 | // 60 | 61 | // Cairo instance 62 | static cairo_t *Clay__Cairo = NULL; 63 | 64 | // Return a null-terminated copy of Clay_String `str`. 65 | // Callee is required to free. 66 | static inline char *Clay_Cairo__NullTerminate(Clay_String *str) { 67 | char *copy = (char*) malloc(str->length + 1); 68 | if (!copy) { 69 | fprintf(stderr, "Memory allocation failed\n"); 70 | return NULL; 71 | } 72 | memcpy(copy, str->chars, str->length); 73 | copy[str->length] = '\0'; 74 | return copy; 75 | } 76 | 77 | // Measure text using cairo's *toy* text API. 78 | static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { 79 | // Edge case: Clay computes the width of a whitespace character 80 | // once. Cairo does not factor in whitespaces when computing text 81 | // extents, this edge-case serves as a short-circuit to introduce 82 | // (somewhat) sensible values into Clay. 83 | char** fonts = (char**)userData; 84 | if(str.length == 1 && str.chars[0] == ' ') { 85 | cairo_text_extents_t te; 86 | cairo_text_extents(Clay__Cairo, " ", &te); 87 | return (Clay_Dimensions) { 88 | // The multiplication here follows no real logic, just 89 | // brute-forcing it until the text boundaries look 90 | // okay-ish. You should probably rather use a proper text 91 | // shaping engine like HarfBuzz or Pango. 92 | .width = ((float) te.x_advance) * 1.9f, 93 | .height = (float) config->fontSize 94 | }; 95 | } 96 | 97 | // Ensure string is null-terminated for Cairo 98 | Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; 99 | char *text = Clay_Cairo__NullTerminate(&toTerminate); 100 | char *font_family = fonts[config->fontId]; 101 | 102 | // Save and reset the Cairo context to avoid unwanted transformations 103 | cairo_save(Clay__Cairo); 104 | cairo_identity_matrix(Clay__Cairo); 105 | 106 | // Set font properties 107 | cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 108 | cairo_set_font_size(Clay__Cairo, config->fontSize); 109 | 110 | // Use glyph extents for better precision 111 | cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(Clay__Cairo); 112 | if (!scaled_font) { 113 | fprintf(stderr, "Failed to get scaled font\n"); 114 | cairo_restore(Clay__Cairo); 115 | free(text); 116 | return (Clay_Dimensions){0, 0}; 117 | } 118 | 119 | cairo_glyph_t *glyphs = NULL; 120 | int num_glyphs = 0; 121 | cairo_status_t status = cairo_scaled_font_text_to_glyphs( 122 | scaled_font, 0, 0, text, -1, &glyphs, &num_glyphs, NULL, NULL, NULL 123 | ); 124 | 125 | if (status != CAIRO_STATUS_SUCCESS || !glyphs || num_glyphs == 0) { 126 | fprintf(stderr, "Failed to generate glyphs: %s\n", cairo_status_to_string(status)); 127 | cairo_restore(Clay__Cairo); 128 | free(text); 129 | return (Clay_Dimensions){0, 0}; 130 | } 131 | 132 | // Measure the glyph extents 133 | cairo_text_extents_t glyph_extents; 134 | cairo_glyph_extents(Clay__Cairo, glyphs, num_glyphs, &glyph_extents); 135 | 136 | // Clean up glyphs 137 | cairo_glyph_free(glyphs); 138 | 139 | // Restore the Cairo context 140 | cairo_restore(Clay__Cairo); 141 | 142 | // Free temporary strings 143 | free(text); 144 | 145 | // Return dimensions 146 | return (Clay_Dimensions){ 147 | .width = (float) glyph_extents.width, 148 | .height = (float) glyph_extents.height 149 | }; 150 | } 151 | 152 | 153 | void Clay_Cairo_Initialize(cairo_t *cairo) { 154 | Clay__Cairo = cairo; 155 | } 156 | 157 | // Internally used to copy images onto our document/active workspace. 158 | void Clay_Cairo__Blit_Surface(cairo_surface_t *src_surface, cairo_surface_t *dest_surface, 159 | double x, double y, double scale_x, double scale_y) { 160 | // Create a cairo context for the destination surface 161 | cairo_t *cr = cairo_create(dest_surface); 162 | 163 | // Save the context's state 164 | cairo_save(cr); 165 | 166 | // Apply translation to position the source at (x, y) 167 | cairo_translate(cr, x, y); 168 | 169 | // Apply scaling to the context 170 | cairo_scale(cr, scale_x, scale_y); 171 | 172 | // Set the source surface at (0, 0) after applying transformations 173 | cairo_set_source_surface(cr, src_surface, 0, 0); 174 | 175 | // Paint the scaled source surface onto the destination surface 176 | cairo_paint(cr); 177 | 178 | // Restore the context's state to remove transformations 179 | cairo_restore(cr); 180 | 181 | // Clean up 182 | cairo_destroy(cr); 183 | } 184 | 185 | void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts) { 186 | cairo_t *cr = Clay__Cairo; 187 | for(size_t i = 0; i < commands.length; i++) { 188 | Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); 189 | 190 | switch(command->commandType) { 191 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 192 | Clay_RectangleRenderData *config = &command->renderData.rectangle; 193 | Clay_BoundingBox bb = command->boundingBox; 194 | 195 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->backgroundColor)); 196 | 197 | cairo_new_sub_path(cr); 198 | cairo_arc(cr, bb.x + config->cornerRadius.topLeft, 199 | bb.y + config->cornerRadius.topLeft, 200 | config->cornerRadius.topLeft, 201 | M_PI, 3 * M_PI / 2); // 180° to 270° 202 | cairo_arc(cr, bb.x + bb.width - config->cornerRadius.topRight, 203 | bb.y + config->cornerRadius.topRight, 204 | config->cornerRadius.topRight, 205 | 3 * M_PI / 2, 2 * M_PI); // 270° to 360° 206 | cairo_arc(cr, bb.x + bb.width - config->cornerRadius.bottomRight, 207 | bb.y + bb.height - config->cornerRadius.bottomRight, 208 | config->cornerRadius.bottomRight, 209 | 0, M_PI / 2); // 0° to 90° 210 | cairo_arc(cr, bb.x + config->cornerRadius.bottomLeft, 211 | bb.y + bb.height - config->cornerRadius.bottomLeft, 212 | config->cornerRadius.bottomLeft, 213 | M_PI / 2, M_PI); // 90° to 180° 214 | cairo_close_path(cr); 215 | 216 | cairo_fill(cr); 217 | break; 218 | } 219 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 220 | // Cairo expects null terminated strings, we need to clone 221 | // to temporarily introduce one. 222 | Clay_TextRenderData *config = &command->renderData.text; 223 | Clay_String toTerminate = (Clay_String){ .chars = config->stringContents.chars, .length = config->stringContents.length, .isStaticallyAllocated = false }; 224 | char *text = Clay_Cairo__NullTerminate(&toTerminate); 225 | char *font_family = fonts[config->fontId]; 226 | 227 | Clay_BoundingBox bb = command->boundingBox; 228 | Clay_Color color = config->textColor; 229 | 230 | cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); 231 | cairo_set_font_size(cr, config->fontSize); 232 | 233 | cairo_move_to(cr, bb.x, bb.y + bb.height); 234 | 235 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(color)); 236 | cairo_show_text(cr, text); 237 | cairo_close_path(cr); 238 | 239 | free(text); 240 | break; 241 | } 242 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 243 | Clay_BorderRenderData *config = &command->renderData.border; 244 | Clay_BoundingBox bb = command->boundingBox; 245 | 246 | double top_left_radius = config->cornerRadius.topLeft / 2.0; 247 | double top_right_radius = config->cornerRadius.topRight / 2.0; 248 | double bottom_right_radius = config->cornerRadius.bottomRight / 2.0; 249 | double bottom_left_radius = config->cornerRadius.bottomLeft / 2.0; 250 | 251 | // Draw the top border 252 | if (config->width.top > 0) { 253 | cairo_set_line_width(cr, config->width.top); 254 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); 255 | 256 | cairo_new_sub_path(cr); 257 | 258 | // Left half-arc for top-left corner 259 | cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(225), DEG2RAD(270)); 260 | 261 | // Line to right half-arc 262 | cairo_line_to(cr, bb.x + bb.width - top_right_radius, bb.y); 263 | 264 | // Right half-arc for top-right corner 265 | cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(270), DEG2RAD(305)); 266 | 267 | cairo_stroke(cr); 268 | } 269 | 270 | // Draw the right border 271 | if (config->width.right > 0) { 272 | cairo_set_line_width(cr, config->width.right); 273 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); 274 | 275 | cairo_new_sub_path(cr); 276 | 277 | // Top half-arc for top-right corner 278 | cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(305), DEG2RAD(350)); 279 | 280 | // Line to bottom half-arc 281 | cairo_line_to(cr, bb.x + bb.width, bb.y + bb.height - bottom_right_radius); 282 | 283 | // Bottom half-arc for bottom-right corner 284 | cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(0), DEG2RAD(45)); 285 | 286 | cairo_stroke(cr); 287 | } 288 | 289 | // Draw the bottom border 290 | if (config->width.bottom > 0) { 291 | cairo_set_line_width(cr, config->width.bottom); 292 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); 293 | 294 | cairo_new_sub_path(cr); 295 | 296 | // Right half-arc for bottom-right corner 297 | cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(45), DEG2RAD(90)); 298 | 299 | // Line to left half-arc 300 | cairo_line_to(cr, bb.x + bottom_left_radius, bb.y + bb.height); 301 | 302 | // Left half-arc for bottom-left corner 303 | cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(90), DEG2RAD(135)); 304 | 305 | cairo_stroke(cr); 306 | } 307 | 308 | // Draw the left border 309 | if (config->width.left > 0) { 310 | cairo_set_line_width(cr, config->width.left); 311 | cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color)); 312 | 313 | cairo_new_sub_path(cr); 314 | 315 | // Bottom half-arc for bottom-left corner 316 | cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(135), DEG2RAD(180)); 317 | 318 | // Line to top half-arc 319 | cairo_line_to(cr, bb.x, bb.y + top_left_radius); 320 | 321 | // Top half-arc for top-left corner 322 | cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(180), DEG2RAD(225)); 323 | 324 | cairo_stroke(cr); 325 | } 326 | break; 327 | } 328 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 329 | Clay_ImageRenderData *config = &command->renderData.image; 330 | Clay_BoundingBox bb = command->boundingBox; 331 | 332 | char *path = config->imageData; 333 | 334 | cairo_surface_t *surf = cairo_image_surface_create_from_png(path), 335 | *origin = cairo_get_target(cr); 336 | 337 | // Calculate the original image dimensions 338 | double image_w = cairo_image_surface_get_width(surf), 339 | image_h = cairo_image_surface_get_height(surf); 340 | 341 | // Calculate the scaling factor to fit within the bounding box while preserving aspect ratio 342 | double scale_w = bb.width / image_w; 343 | double scale_h = bb.height / image_h; 344 | double scale = (scale_w < scale_h) ? scale_w : scale_h; // Use the smaller scaling factor 345 | 346 | // Apply the same scale to both dimensions to preserve aspect ratio 347 | double scale_x = scale; 348 | double scale_y = scale; 349 | 350 | // Calculate the scaled image dimensions 351 | double scaled_w = image_w * scale_x; 352 | double scaled_h = image_h * scale_y; 353 | 354 | // Adjust the x and y coordinates to center the scaled image within the bounding box 355 | double centered_x = bb.x + (bb.width - scaled_w) / 2.0; 356 | double centered_y = bb.y + (bb.height - scaled_h) / 2.0; 357 | 358 | // Blit the scaled and centered image 359 | Clay_Cairo__Blit_Surface(surf, origin, centered_x, centered_y, scale_x, scale_y); 360 | 361 | // Clean up the source surface 362 | cairo_surface_destroy(surf); 363 | break; 364 | } 365 | case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { 366 | // Slot your custom elements in here. 367 | } 368 | default: { 369 | fprintf(stderr, "Unknown command type %d\n", (int) command->commandType); 370 | } 371 | } 372 | } 373 | } 374 | -------------------------------------------------------------------------------- /renderers/playdate/clay_renderer_playdate.c: -------------------------------------------------------------------------------- 1 | #include "pd_api.h" 2 | #include "../../clay.h" 3 | 4 | // Playdate drawText function expects the number of codepoints to draw, not byte length 5 | static size_t Clay_Playdate_CountUtf8Codepoints(const char *str, size_t byteLen) { 6 | size_t count = 0; 7 | size_t i = 0; 8 | while (i < byteLen) { 9 | uint8_t c = (uint8_t)str[i]; 10 | if ((c & 0xC0) != 0x80) { 11 | count++; 12 | } 13 | i++; 14 | } 15 | return count; 16 | } 17 | 18 | // As the playdate can only display black and white, we need to resolve Clay_color to either black or white 19 | // for both color and draw mode. 20 | static LCDColor clayColorToLCDColor(Clay_Color color) { 21 | if (color.r > 0 || color.g > 0 || color.b > 0) { 22 | return kColorWhite; 23 | } 24 | return kColorBlack; 25 | } 26 | 27 | static LCDBitmapDrawMode clayColorToDrawMode(Clay_Color color) { 28 | if (color.r > 0 || color.g > 0 || color.b > 0) { 29 | return kDrawModeFillWhite; 30 | } 31 | return kDrawModeCopy; 32 | } 33 | 34 | static float clampCornerRadius(float yAxisSize, float radius) { 35 | if (radius < 1.0f) { 36 | return 0.0f; 37 | } 38 | if (radius > yAxisSize / 2) { 39 | return yAxisSize / 2; 40 | } 41 | // Trying to draw a 2x2 ellipse seems to result in just a dot, so if 42 | // there is a corner radius at minimum it must be 2 43 | return CLAY__MAX(2, radius); 44 | } 45 | 46 | static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray renderCommands, LCDFont **fonts) { 47 | for (uint32_t i = 0; i < renderCommands.length; i++) { 48 | Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); 49 | Clay_BoundingBox boundingBox = renderCommand->boundingBox; 50 | 51 | switch (renderCommand->commandType) { 52 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 53 | Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; 54 | 55 | float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); 56 | float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); 57 | float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); 58 | float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); 59 | 60 | pd->graphics->fillEllipse( 61 | boundingBox.x, boundingBox.y, 62 | radiusTl * 2, radiusTl * 2, 63 | -90.0f, 0.0f, 64 | clayColorToLCDColor(config->backgroundColor) 65 | ); 66 | 67 | pd->graphics->fillEllipse( 68 | boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, 69 | radiusTr * 2, radiusTr * 2, 70 | 0.0f, 90.0f, 71 | clayColorToLCDColor(config->backgroundColor) 72 | ); 73 | 74 | pd->graphics->fillEllipse( 75 | boundingBox.x + boundingBox.width - radiusBr * 2, 76 | boundingBox.y + boundingBox.height - radiusBr * 2, 77 | radiusBr * 2, radiusBr * 2, 78 | 90.0f, 180.0f, 79 | clayColorToLCDColor(config->backgroundColor) 80 | ); 81 | 82 | pd->graphics->fillEllipse( 83 | boundingBox.x, 84 | boundingBox.y + boundingBox.height - radiusBl * 2, 85 | radiusBl * 2, radiusBl * 2, 86 | 180.0f, 270.0f, 87 | clayColorToLCDColor(config->backgroundColor) 88 | ); 89 | 90 | // Top chunk 91 | pd->graphics->fillRect( 92 | boundingBox.x + radiusTl, boundingBox.y, 93 | boundingBox.width - radiusTl - radiusTr, 94 | CLAY__MAX(radiusTl, radiusTr), 95 | clayColorToLCDColor(config->backgroundColor) 96 | ); 97 | 98 | // bottom chunk 99 | int bottomChunkHeight = CLAY__MAX(radiusBl, radiusBr); 100 | pd->graphics->fillRect( 101 | boundingBox.x + radiusBl, boundingBox.y + boundingBox.height - bottomChunkHeight, 102 | boundingBox.width - radiusBl - radiusBr, 103 | bottomChunkHeight, 104 | clayColorToLCDColor(config->backgroundColor) 105 | ); 106 | 107 | // Middle chunk 108 | int middleChunkHeight = boundingBox.height - CLAY__MIN(radiusBr, radiusBl) - CLAY__MIN(radiusTr, radiusTl); 109 | pd->graphics->fillRect( 110 | boundingBox.x + CLAY__MIN(radiusTl, radiusBl), boundingBox.y + CLAY__MIN(radiusTr, radiusTl), 111 | boundingBox.width - radiusBl - radiusBr, 112 | middleChunkHeight, 113 | clayColorToLCDColor(config->backgroundColor) 114 | ); 115 | 116 | // Left chunk 117 | int leftChunkHeight = boundingBox.height - radiusTl - radiusBl; 118 | int leftChunkWidth = CLAY__MAX(radiusTl, radiusBl); 119 | pd->graphics->fillRect( 120 | boundingBox.x, boundingBox.y + radiusTl, 121 | leftChunkWidth, 122 | leftChunkHeight, 123 | clayColorToLCDColor(config->backgroundColor) 124 | ); 125 | 126 | // Right chunk 127 | int rightChunkHeight = boundingBox.height - radiusTr - radiusBr; 128 | int rightChunkWidth = CLAY__MAX(radiusTr, radiusBr); 129 | pd->graphics->fillRect( 130 | boundingBox.x + boundingBox.width - rightChunkWidth, boundingBox.y + radiusTr, 131 | rightChunkWidth, 132 | rightChunkHeight, 133 | clayColorToLCDColor(config->backgroundColor) 134 | ); 135 | break; 136 | } 137 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 138 | Clay_TextRenderData *config = &renderCommand->renderData.text; 139 | LCDFont *font = fonts[config->fontId]; 140 | pd->graphics->setFont(font); 141 | pd->graphics->setDrawMode(clayColorToDrawMode(config->textColor)); 142 | pd->graphics->drawText( 143 | renderCommand->renderData.text.stringContents.chars, 144 | Clay_Playdate_CountUtf8Codepoints( 145 | renderCommand->renderData.text.stringContents.chars, 146 | renderCommand->renderData.text.stringContents.length 147 | ), 148 | kUTF8Encoding, 149 | boundingBox.x, 150 | boundingBox.y 151 | ); 152 | pd->graphics->setDrawMode(kDrawModeCopy); 153 | break; 154 | } 155 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { 156 | pd->graphics->setClipRect( 157 | boundingBox.x,boundingBox.y, 158 | boundingBox.width, boundingBox.height 159 | ); 160 | break; 161 | } 162 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { 163 | pd->graphics->clearClipRect(); 164 | break; 165 | } 166 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 167 | Clay_ImageRenderData *config = &renderCommand->renderData.image; 168 | LCDBitmap *texture = config->imageData; 169 | int texWidth; 170 | int texHeight; 171 | pd->graphics->getBitmapData(texture, &texWidth, &texHeight, NULL, NULL, NULL); 172 | if (texWidth != boundingBox.width || texHeight != boundingBox.height) { 173 | pd->graphics->drawScaledBitmap( 174 | texture, 175 | boundingBox.x, boundingBox.y, 176 | boundingBox.width / texWidth, 177 | boundingBox.height / texHeight 178 | ); 179 | } else { 180 | pd->graphics->drawBitmap(texture, boundingBox.x, boundingBox.y, kBitmapUnflipped); 181 | } 182 | break; 183 | } 184 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 185 | Clay_BorderRenderData *config = &renderCommand->renderData.border; 186 | 187 | float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); 188 | float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); 189 | float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); 190 | float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); 191 | 192 | if (config->width.top > 0) { 193 | pd->graphics->drawEllipse( 194 | boundingBox.x, boundingBox.y, 195 | radiusTl * 2, radiusTl * 2, 196 | config->width.top, 197 | -90.0f, 0.0f, 198 | clayColorToLCDColor(config->color) 199 | ); 200 | 201 | pd->graphics->drawLine( 202 | boundingBox.x + radiusTl, boundingBox.y, 203 | boundingBox.x + boundingBox.width - radiusTr - config->width.right, boundingBox.y, 204 | config->width.top, 205 | clayColorToLCDColor(config->color) 206 | ); 207 | 208 | pd->graphics->drawEllipse( 209 | boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, 210 | radiusTr * 2, radiusTr * 2, 211 | config->width.top, 212 | 0.0f, 90.0f, 213 | clayColorToLCDColor(config->color) 214 | ); 215 | } 216 | 217 | if (config->width.right > 0 && radiusTr + radiusBr <= boundingBox.height) { 218 | pd->graphics->drawLine( 219 | boundingBox.x + boundingBox.width - config->width.right, 220 | boundingBox.y + radiusTr, 221 | boundingBox.x + boundingBox.width - config->width.right, 222 | boundingBox.y + boundingBox.height - radiusBr - config->width.bottom, 223 | config->width.right, 224 | clayColorToLCDColor(config->color) 225 | ); 226 | } 227 | 228 | if (config->width.bottom > 0) { 229 | pd->graphics->drawEllipse( 230 | boundingBox.x + boundingBox.width - radiusBr * 2, 231 | boundingBox.y + boundingBox.height - radiusBr * 2, 232 | radiusBr * 2, radiusBr * 2, 233 | config->width.bottom, 234 | 90.0f, 180.0f, 235 | clayColorToLCDColor(config->color) 236 | ); 237 | 238 | pd->graphics->drawLine( 239 | boundingBox.x + boundingBox.width - radiusBr - config->width.right, 240 | boundingBox.y + boundingBox.height - config->width.bottom, 241 | boundingBox.x + radiusBl, 242 | boundingBox.y + boundingBox.height - config->width.bottom, 243 | config->width.bottom, 244 | clayColorToLCDColor(config->color) 245 | ); 246 | 247 | pd->graphics->drawEllipse( 248 | boundingBox.x, 249 | boundingBox.y + boundingBox.height - radiusBl * 2, 250 | radiusBl * 2, radiusBl * 2, 251 | config->width.bottom, 252 | 180.0f, 270.0f, 253 | clayColorToLCDColor(config->color) 254 | ); 255 | } 256 | 257 | if (config->width.left > 0 && radiusBl + radiusTl < boundingBox.height) { 258 | pd->graphics->drawLine( 259 | boundingBox.x, boundingBox.y + boundingBox.height - radiusBl - config->width.bottom, 260 | boundingBox.x, boundingBox.y + radiusTl, 261 | config->width.left, 262 | clayColorToLCDColor(config->color) 263 | ); 264 | } 265 | break; 266 | } 267 | default: { 268 | pd->system->logToConsole("Error: unhandled render command: %d\n", renderCommand->commandType); 269 | return; 270 | } 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /renderers/raylib/clay_renderer_raylib.c: -------------------------------------------------------------------------------- 1 | #include "raylib.h" 2 | #include "raymath.h" 3 | #include "stdint.h" 4 | #include "string.h" 5 | #include "stdio.h" 6 | #include "stdlib.h" 7 | 8 | #define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height } 9 | #define CLAY_COLOR_TO_RAYLIB_COLOR(color) (Color) { .r = (unsigned char)roundf(color.r), .g = (unsigned char)roundf(color.g), .b = (unsigned char)roundf(color.b), .a = (unsigned char)roundf(color.a) } 10 | 11 | Camera Raylib_camera; 12 | 13 | typedef enum 14 | { 15 | CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL 16 | } CustomLayoutElementType; 17 | 18 | typedef struct 19 | { 20 | Model model; 21 | float scale; 22 | Vector3 position; 23 | Matrix rotation; 24 | } CustomLayoutElement_3DModel; 25 | 26 | typedef struct 27 | { 28 | CustomLayoutElementType type; 29 | union { 30 | CustomLayoutElement_3DModel model; 31 | } customData; 32 | } CustomLayoutElement; 33 | 34 | // Get a ray trace from the screen position (i.e mouse) within a specific section of the screen 35 | Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int screenWidth, int screenHeight, float zDistance) 36 | { 37 | Ray ray = { 0 }; 38 | 39 | // Calculate normalized device coordinates 40 | // NOTE: y value is negative 41 | float x = (2.0f*position.x)/(float)screenWidth - 1.0f; 42 | float y = 1.0f - (2.0f*position.y)/(float)screenHeight; 43 | float z = 1.0f; 44 | 45 | // Store values in a vector 46 | Vector3 deviceCoords = { x, y, z }; 47 | 48 | // Calculate view matrix from camera look at 49 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); 50 | 51 | Matrix matProj = MatrixIdentity(); 52 | 53 | if (camera.projection == CAMERA_PERSPECTIVE) 54 | { 55 | // Calculate projection matrix from perspective 56 | matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)screenWidth/(double)screenHeight), 0.01f, zDistance); 57 | } 58 | else if (camera.projection == CAMERA_ORTHOGRAPHIC) 59 | { 60 | double aspect = (double)screenWidth/(double)screenHeight; 61 | double top = camera.fovy/2.0; 62 | double right = top*aspect; 63 | 64 | // Calculate projection matrix from orthographic 65 | matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); 66 | } 67 | 68 | // Unproject far/near points 69 | Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); 70 | Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); 71 | 72 | // Calculate normalized direction vector 73 | Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); 74 | 75 | ray.position = farPoint; 76 | 77 | // Apply calculated vectors to ray 78 | ray.direction = direction; 79 | 80 | return ray; 81 | } 82 | 83 | 84 | static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { 85 | // Measure string size for Font 86 | Clay_Dimensions textSize = { 0 }; 87 | 88 | float maxTextWidth = 0.0f; 89 | float lineTextWidth = 0; 90 | 91 | float textHeight = config->fontSize; 92 | Font* fonts = (Font*)userData; 93 | Font fontToUse = fonts[config->fontId]; 94 | // Font failed to load, likely the fonts are in the wrong place relative to the execution dir. 95 | // RayLib ships with a default font, so we can continue with that built in one. 96 | if (!fontToUse.glyphs) { 97 | fontToUse = GetFontDefault(); 98 | } 99 | 100 | float scaleFactor = config->fontSize/(float)fontToUse.baseSize; 101 | 102 | for (int i = 0; i < text.length; ++i) 103 | { 104 | if (text.chars[i] == '\n') { 105 | maxTextWidth = fmax(maxTextWidth, lineTextWidth); 106 | lineTextWidth = 0; 107 | continue; 108 | } 109 | int index = text.chars[i] - 32; 110 | if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX; 111 | else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX); 112 | } 113 | 114 | maxTextWidth = fmax(maxTextWidth, lineTextWidth); 115 | 116 | textSize.width = maxTextWidth * scaleFactor; 117 | textSize.height = textHeight; 118 | 119 | return textSize; 120 | } 121 | 122 | void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) { 123 | SetConfigFlags(flags); 124 | InitWindow(width, height, title); 125 | // EnableEventWaiting(); 126 | } 127 | 128 | // A MALLOC'd buffer, that we keep modifying inorder to save from so many Malloc and Free Calls. 129 | // Call Clay_Raylib_Close() to free 130 | static char *temp_render_buffer = NULL; 131 | static int temp_render_buffer_len = 0; 132 | 133 | // Call after closing the window to clean up the render buffer 134 | void Clay_Raylib_Close() 135 | { 136 | if(temp_render_buffer) free(temp_render_buffer); 137 | temp_render_buffer_len = 0; 138 | 139 | CloseWindow(); 140 | } 141 | 142 | 143 | void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) 144 | { 145 | for (int j = 0; j < renderCommands.length; j++) 146 | { 147 | Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); 148 | Clay_BoundingBox boundingBox = renderCommand->boundingBox; 149 | switch (renderCommand->commandType) 150 | { 151 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 152 | Clay_TextRenderData *textData = &renderCommand->renderData.text; 153 | Font fontToUse = fonts[textData->fontId]; 154 | 155 | int strlen = textData->stringContents.length + 1; 156 | 157 | if(strlen > temp_render_buffer_len) { 158 | // Grow the temp buffer if we need a larger string 159 | if(temp_render_buffer) free(temp_render_buffer); 160 | temp_render_buffer = (char *) malloc(strlen); 161 | temp_render_buffer_len = strlen; 162 | } 163 | 164 | // Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator 165 | memcpy(temp_render_buffer, textData->stringContents.chars, textData->stringContents.length); 166 | temp_render_buffer[textData->stringContents.length] = '\0'; 167 | DrawTextEx(fontToUse, temp_render_buffer, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor)); 168 | 169 | break; 170 | } 171 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 172 | Texture2D imageTexture = *(Texture2D *)renderCommand->renderData.image.imageData; 173 | Clay_Color tintColor = renderCommand->renderData.image.backgroundColor; 174 | if (tintColor.r == 0 && tintColor.g == 0 && tintColor.b == 0 && tintColor.a == 0) { 175 | tintColor = (Clay_Color) { 255, 255, 255, 255 }; 176 | } 177 | DrawTexturePro( 178 | imageTexture, 179 | (Rectangle) { 0, 0, imageTexture.width, imageTexture.height }, 180 | (Rectangle){boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, 181 | (Vector2) {}, 182 | 0, 183 | CLAY_COLOR_TO_RAYLIB_COLOR(tintColor)); 184 | break; 185 | } 186 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { 187 | BeginScissorMode((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height)); 188 | break; 189 | } 190 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { 191 | EndScissorMode(); 192 | break; 193 | } 194 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 195 | Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; 196 | if (config->cornerRadius.topLeft > 0) { 197 | float radius = (config->cornerRadius.topLeft * 2) / (float)((boundingBox.width > boundingBox.height) ? boundingBox.height : boundingBox.width); 198 | DrawRectangleRounded((Rectangle) { boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height }, radius, 8, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor)); 199 | } else { 200 | DrawRectangle(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor)); 201 | } 202 | break; 203 | } 204 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 205 | Clay_BorderRenderData *config = &renderCommand->renderData.border; 206 | // Left border 207 | if (config->width.left > 0) { 208 | DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->width.left, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 209 | } 210 | // Right border 211 | if (config->width.right > 0) { 212 | DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->width.right), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->width.right, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 213 | } 214 | // Top border 215 | if (config->width.top > 0) { 216 | DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->width.top, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 217 | } 218 | // Bottom border 219 | if (config->width.bottom > 0) { 220 | DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->width.bottom), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->width.bottom, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 221 | } 222 | if (config->cornerRadius.topLeft > 0) { 223 | DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->width.top), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 224 | } 225 | if (config->cornerRadius.topRight > 0) { 226 | DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->width.top), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 227 | } 228 | if (config->cornerRadius.bottomLeft > 0) { 229 | DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.bottom), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 230 | } 231 | if (config->cornerRadius.bottomRight > 0) { 232 | DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color)); 233 | } 234 | break; 235 | } 236 | case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { 237 | Clay_CustomRenderData *config = &renderCommand->renderData.custom; 238 | CustomLayoutElement *customElement = (CustomLayoutElement *)config->customData; 239 | if (!customElement) continue; 240 | switch (customElement->type) { 241 | case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { 242 | Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox; 243 | float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f); 244 | Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140); 245 | BeginMode3D(Raylib_camera); 246 | DrawModel(customElement->customData.model.model, positionRay.position, customElement->customData.model.scale * scaleValue, WHITE); // Draw 3d model with texture 247 | EndMode3D(); 248 | break; 249 | } 250 | default: break; 251 | } 252 | break; 253 | } 254 | default: { 255 | printf("Error: unhandled render command."); 256 | exit(1); 257 | } 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /renderers/terminal/clay_renderer_terminal_ansi.c: -------------------------------------------------------------------------------- 1 | #include "stdint.h" 2 | #include "string.h" 3 | #include "stdio.h" 4 | #include "stdlib.h" 5 | 6 | #ifdef CLAY_OVERFLOW_TRAP 7 | #include "signal.h" 8 | #endif 9 | 10 | static inline void Console_MoveCursor(int x, int y) { 11 | printf("\033[%d;%dH", y + 1, x + 1); 12 | } 13 | 14 | bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { 15 | // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want 16 | // TODO to expose Clay__PointIsInsideRect 17 | return point.x >= rect.x && point.x < rect.x + rect.width && point.y >= rect.y && point.y < rect.y + rect.height; 18 | } 19 | 20 | static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, 21 | Clay_BoundingBox scissorBox) { 22 | float average = (color.r + color.g + color.b + color.a) / 4 / 255; 23 | 24 | for (int y = y0; y < height + y0; y++) { 25 | for (int x = x0; x < width + x0; x++) { 26 | if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = x, .y = y}, scissorBox)) { 27 | continue; 28 | } 29 | 30 | Console_MoveCursor(x, y); 31 | // TODO this should be replaced by a better logarithmic scale if we're doing black and white 32 | if (average > 0.75) { 33 | printf("█"); 34 | } else if (average > 0.5) { 35 | printf("▓"); 36 | } else if (average > 0.25) { 37 | printf("▒"); 38 | } else { 39 | printf("░"); 40 | } 41 | } 42 | } 43 | } 44 | 45 | static inline Clay_Dimensions 46 | Console_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { 47 | Clay_Dimensions textSize = {0}; 48 | int columnWidth = *(int *) userData; 49 | 50 | // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels 51 | 52 | float maxTextWidth = 0.0f; 53 | float lineTextWidth = 0; 54 | 55 | float textHeight = 1; 56 | 57 | for (int i = 0; i < text.length; ++i) { 58 | if (text.chars[i] == '\n') { 59 | maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; 60 | lineTextWidth = 0; 61 | textHeight++; 62 | continue; 63 | } 64 | lineTextWidth++; 65 | } 66 | 67 | maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; 68 | 69 | textSize.width = maxTextWidth * columnWidth; 70 | textSize.height = textHeight * columnWidth; 71 | 72 | return textSize; 73 | } 74 | 75 | void Clay_Terminal_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth) { 76 | printf("\033[H\033[J"); // Clear 77 | 78 | const Clay_BoundingBox fullWindow = { 79 | .x = 0, 80 | .y = 0, 81 | .width = (float) width, 82 | .height = (float) height, 83 | }; 84 | 85 | Clay_BoundingBox scissorBox = fullWindow; 86 | 87 | for (int j = 0; j < renderCommands.length; j++) { 88 | Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); 89 | Clay_BoundingBox boundingBox = (Clay_BoundingBox) { 90 | .x = (int)((renderCommand->boundingBox.x / columnWidth) + 0.5), 91 | .y = (int)((renderCommand->boundingBox.y / columnWidth) + 0.5), 92 | .width = (int)((renderCommand->boundingBox.width / columnWidth) + 0.5), 93 | .height = (int)((renderCommand->boundingBox.height / columnWidth) + 0.5), 94 | }; 95 | switch (renderCommand->commandType) { 96 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 97 | Clay_TextRenderData data = renderCommand->renderData.text; 98 | Clay_StringSlice text = data.stringContents; 99 | int y = 0; 100 | for (int x = 0; x < text.length; x++) { 101 | if (text.chars[x] == '\n') { 102 | y++; 103 | continue; 104 | } 105 | 106 | int cursorX = (int) boundingBox.x + x; 107 | int cursorY = (int) boundingBox.y + y; 108 | if (cursorY > scissorBox.y + scissorBox.height) { 109 | break; 110 | } 111 | if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = cursorX, .y = cursorY}, scissorBox)) { 112 | continue; 113 | } 114 | 115 | Console_MoveCursor(cursorX, cursorY); 116 | printf("%c", text.chars[x]); 117 | } 118 | break; 119 | } 120 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { 121 | scissorBox = boundingBox; 122 | break; 123 | } 124 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { 125 | scissorBox = fullWindow; 126 | break; 127 | } 128 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 129 | Clay_RectangleRenderData data = renderCommand->renderData.rectangle; 130 | Console_DrawRectangle( 131 | (int) boundingBox.x, 132 | (int) boundingBox.y, 133 | (int) boundingBox.width, 134 | (int) boundingBox.height, 135 | data.backgroundColor, 136 | scissorBox); 137 | break; 138 | } 139 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 140 | Clay_BorderRenderData data = renderCommand->renderData.border; 141 | // Left border 142 | if (data.width.left > 0) { 143 | Console_DrawRectangle( 144 | (int) (boundingBox.x), 145 | (int) (boundingBox.y + data.cornerRadius.topLeft), 146 | (int) data.width.left, 147 | (int) (boundingBox.height - data.cornerRadius.topLeft - data.cornerRadius.bottomLeft), 148 | data.color, 149 | scissorBox); 150 | } 151 | // Right border 152 | if (data.width.right > 0) { 153 | Console_DrawRectangle( 154 | (int) (boundingBox.x + boundingBox.width - data.width.right), 155 | (int) (boundingBox.y + data.cornerRadius.topRight), 156 | (int) data.width.right, 157 | (int) (boundingBox.height - data.cornerRadius.topRight - data.cornerRadius.bottomRight), 158 | data.color, 159 | scissorBox); 160 | } 161 | // Top border 162 | if (data.width.top > 0) { 163 | Console_DrawRectangle( 164 | (int) (boundingBox.x + data.cornerRadius.topLeft), 165 | (int) (boundingBox.y), 166 | (int) (boundingBox.width - data.cornerRadius.topLeft - data.cornerRadius.topRight), 167 | (int) data.width.top, 168 | data.color, 169 | scissorBox); 170 | } 171 | // Bottom border 172 | if (data.width.bottom > 0) { 173 | Console_DrawRectangle( 174 | (int) (boundingBox.x + data.cornerRadius.bottomLeft), 175 | (int) (boundingBox.y + boundingBox.height - data.width.bottom), 176 | (int) (boundingBox.width - data.cornerRadius.bottomLeft - data.cornerRadius.bottomRight), 177 | (int) data.width.bottom, 178 | data.color, 179 | scissorBox); 180 | } 181 | break; 182 | } 183 | default: { 184 | printf("Error: unhandled render command."); 185 | #ifdef CLAY_OVERFLOW_TRAP 186 | raise(SIGTRAP); 187 | #endif 188 | exit(1); 189 | } 190 | } 191 | } 192 | 193 | Console_MoveCursor(-1, -1); // TODO make the user not be able to write 194 | } 195 | -------------------------------------------------------------------------------- /renderers/web/build-wasm.sh: -------------------------------------------------------------------------------- 1 | cp ../../clay.h clay.c && \ 2 | clang \ 3 | -Os \ 4 | -DCLAY_WASM \ 5 | -mbulk-memory \ 6 | --target=wasm32 \ 7 | -nostdlib \ 8 | -Wl,--strip-all \ 9 | -Wl,--export-dynamic \ 10 | -Wl,--no-entry \ 11 | -Wl,--export=__heap_base \ 12 | -Wl,--initial-memory=6553600 \ 13 | -o clay.wasm clay.c; rm clay.c; -------------------------------------------------------------------------------- /renderers/web/clay.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicbarker/clay/a3003cfa1219309d8a90f2f2384fdbb183e35195/renderers/web/clay.wasm -------------------------------------------------------------------------------- /renderers/win32_gdi/README.md: -------------------------------------------------------------------------------- 1 | The windows GDI renderer example is missing the following: 2 | 3 | - Images 4 | - Rendering Rounded Rectangle borders 5 | - Custom Fonts (font size) 6 | -------------------------------------------------------------------------------- /tests/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | gcc: 3 | build: 4 | context: ../ 5 | dockerfile: tests/gcc/9.4/Dockerfile 6 | volumes: 7 | - /tmp/clay/_deps -------------------------------------------------------------------------------- /tests/gcc/9.4/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 ubuntu:20.04 2 | 3 | RUN apt update -y 4 | RUN DEBIAN_FRONTEND=noninteractive apt install -y build-essential 5 | RUN DEBIAN_FRONTEND=noninteractive apt install -y wget 6 | WORKDIR /tmp/ 7 | RUN wget https://github.com/Kitware/CMake/releases/download/v3.28.4/cmake-3.28.4-linux-x86_64.tar.gz 8 | RUN tar zxvf cmake-3.28.4-linux-x86_64.tar.gz 9 | RUN DEBIAN_FRONTEND=noninteractive apt install -y git 10 | RUN DEBIAN_FRONTEND=noninteractive apt install -y libwayland-dev 11 | RUN DEBIAN_FRONTEND=noninteractive apt install -y pkg-config 12 | RUN DEBIAN_FRONTEND=noninteractive apt install -y libxkbcommon-dev 13 | RUN DEBIAN_FRONTEND=noninteractive apt install -y xorg-dev 14 | 15 | ADD . /tmp/clay 16 | 17 | WORKDIR /tmp/clay 18 | 19 | CMD /tmp/cmake-3.28.4-linux-x86_64/bin/cmake . && /tmp/cmake-3.28.4-linux-x86_64/bin/cmake --build . -------------------------------------------------------------------------------- /tests/run-tests.sh: -------------------------------------------------------------------------------- 1 | docker compose build && docker compose up && echo "Tests complete." --------------------------------------------------------------------------------